UNPKG

133 kBJavaScriptView Raw
1/**
2 * @license
3 * (c) 2009-2016 Michael Leibman
4 * michael{dot}leibman{at}gmail{dot}com
5 * http://github.com/mleibman/slickgrid
6 *
7 * Distributed under MIT license.
8 * All rights reserved.
9 *
10 * SlickGrid v2.3
11 *
12 * NOTES:
13 * Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods.
14 * This increases the speed dramatically, but can only be done safely because there are no event handlers
15 * or data associated with any cell/row DOM nodes. Cell editors must make sure they implement .destroy()
16 * and do proper cleanup.
17 */
18
19// make sure required JavaScript modules are loaded
20if (typeof jQuery === "undefined") {
21 throw new Error("SlickGrid requires jquery module to be loaded");
22}
23if (!jQuery.fn.drag) {
24 throw new Error("SlickGrid requires jquery.event.drag module to be loaded");
25}
26if (typeof Slick === "undefined") {
27 throw new Error("slick.core.js not loaded");
28}
29
30
31(function ($) {
32 // Slick.Grid
33 $.extend(true, window, {
34 Slick: {
35 Grid: SlickGrid
36 }
37 });
38
39 // shared across all grids on the page
40 var scrollbarDimensions;
41 var maxSupportedCssHeight; // browser's breaking point
42
43 //////////////////////////////////////////////////////////////////////////////////////////////
44 // SlickGrid class implementation (available as Slick.Grid)
45
46 /**
47 * Creates a new instance of the grid.
48 * @class SlickGrid
49 * @constructor
50 * @param {Node} container Container node to create the grid in.
51 * @param {Array,Object} data An array of objects for databinding.
52 * @param {Array} columns An array of column definitions.
53 * @param {Object} options Grid options.
54 **/
55 function SlickGrid(container, data, columns, options) {
56 // settings
57 var defaults = {
58 alwaysShowVerticalScroll: false,
59 explicitInitialization: false,
60 rowHeight: 25,
61 defaultColumnWidth: 80,
62 enableAddRow: false,
63 leaveSpaceForNewRows: false,
64 editable: false,
65 autoEdit: true,
66 suppressActiveCellChangeOnEdit: false,
67 enableCellNavigation: true,
68 enableColumnReorder: true,
69 asyncEditorLoading: false,
70 asyncEditorLoadDelay: 100,
71 forceFitColumns: false,
72 enableAsyncPostRender: false,
73 asyncPostRenderDelay: 50,
74 enableAsyncPostRenderCleanup: false,
75 asyncPostRenderCleanupDelay: 40,
76 autoHeight: false,
77 editorLock: Slick.GlobalEditorLock,
78 showHeaderRow: false,
79 headerRowHeight: 25,
80 createFooterRow: false,
81 showFooterRow: false,
82 footerRowHeight: 25,
83 createPreHeaderPanel: false,
84 showPreHeaderPanel: false,
85 preHeaderPanelHeight: 25,
86 showTopPanel: false,
87 topPanelHeight: 25,
88 formatterFactory: null,
89 editorFactory: null,
90 cellFlashingCssClass: "flashing",
91 selectedCellCssClass: "selected",
92 multiSelect: true,
93 enableTextSelectionOnCells: false,
94 dataItemColumnValueExtractor: null,
95 fullWidthRows: false,
96 multiColumnSort: false,
97 numberedMultiColumnSort: false,
98 tristateMultiColumnSort: false,
99 sortColNumberInSeparateSpan: false,
100 defaultFormatter: defaultFormatter,
101 forceSyncScrolling: false,
102 addNewRowCssClass: "new-row",
103 preserveCopiedSelectionOnPaste: false,
104 showCellSelection: true,
105 viewportClass: null,
106 minRowBuffer: 3,
107 emulatePagingWhenScrolling: true, // when scrolling off bottom of viewport, place new row at top of viewport
108 editorCellNavOnLRKeys: false
109 };
110
111 var columnDefaults = {
112 name: "",
113 resizable: true,
114 sortable: false,
115 minWidth: 30,
116 rerenderOnResize: false,
117 headerCssClass: null,
118 defaultSortAsc: true,
119 focusable: true,
120 selectable: true
121 };
122
123 // scroller
124 var th; // virtual height
125 var h; // real scrollable height
126 var ph; // page height
127 var n; // number of pages
128 var cj; // "jumpiness" coefficient
129
130 var page = 0; // current page
131 var offset = 0; // current page offset
132 var vScrollDir = 1;
133
134 // private
135 var initialized = false;
136 var $container;
137 var uid = "slickgrid_" + Math.round(1000000 * Math.random());
138 var self = this;
139 var $focusSink, $focusSink2;
140 var $headerScroller;
141 var $headers;
142 var $headerRow, $headerRowScroller, $headerRowSpacer;
143 var $footerRow, $footerRowScroller, $footerRowSpacer;
144 var $preHeaderPanel, $preHeaderPanelScroller, $preHeaderPanelSpacer;
145 var $topPanelScroller;
146 var $topPanel;
147 var $viewport;
148 var $canvas;
149 var $style;
150 var $boundAncestors;
151 var stylesheet, columnCssRulesL, columnCssRulesR;
152 var viewportH, viewportW;
153 var canvasWidth;
154 var viewportHasHScroll, viewportHasVScroll;
155 var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding
156 cellWidthDiff = 0, cellHeightDiff = 0, jQueryNewWidthBehaviour = false;
157 var absoluteColumnMinWidth;
158
159 var tabbingDirection = 1;
160 var activePosX;
161 var activeRow, activeCell;
162 var activeCellNode = null;
163 var currentEditor = null;
164 var serializedEditorValue;
165 var editController;
166
167 var rowsCache = {};
168 var renderedRows = 0;
169 var numVisibleRows;
170 var prevScrollTop = 0;
171 var scrollTop = 0;
172 var lastRenderedScrollTop = 0;
173 var lastRenderedScrollLeft = 0;
174 var prevScrollLeft = 0;
175 var scrollLeft = 0;
176
177 var selectionModel;
178 var selectedRows = [];
179
180 var plugins = [];
181 var cellCssClasses = {};
182
183 var columnsById = {};
184 var sortColumns = [];
185 var columnPosLeft = [];
186 var columnPosRight = [];
187
188 var pagingActive = false;
189 var pagingIsLastPage = false;
190
191 var scrollThrottle = ActionThrottle(render, 50);
192
193 // async call handles
194 var h_editorLoader = null;
195 var h_render = null;
196 var h_postrender = null;
197 var h_postrenderCleanup = null;
198 var postProcessedRows = {};
199 var postProcessToRow = null;
200 var postProcessFromRow = null;
201 var postProcessedCleanupQueue = [];
202 var postProcessgroupId = 0;
203
204 // perf counters
205 var counter_rows_rendered = 0;
206 var counter_rows_removed = 0;
207
208 // These two variables work around a bug with inertial scrolling in Webkit/Blink on Mac.
209 // See http://crbug.com/312427.
210 var rowNodeFromLastMouseWheelEvent; // this node must not be deleted while inertial scrolling
211 var zombieRowNodeFromLastMouseWheelEvent; // node that was hidden instead of getting deleted
212 var zombieRowCacheFromLastMouseWheelEvent; // row cache for above node
213 var zombieRowPostProcessedFromLastMouseWheelEvent; // post processing references for above node
214
215 // store css attributes if display:none is active in container or parent
216 var cssShow = { position: 'absolute', visibility: 'hidden', display: 'block' };
217 var $hiddenParents;
218 var oldProps = [];
219 var columnResizeDragging = false;
220
221 //////////////////////////////////////////////////////////////////////////////////////////////
222 // Initialization
223
224 function init() {
225 if (container instanceof jQuery) {
226 $container = container;
227 } else {
228 $container = $(container);
229 }
230 if ($container.length < 1) {
231 throw new Error("SlickGrid requires a valid container, " + container + " does not exist in the DOM.");
232 }
233
234 cacheCssForHiddenInit();
235
236 // calculate these only once and share between grid instances
237 maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight();
238
239 options = $.extend({}, defaults, options);
240 validateAndEnforceOptions();
241 columnDefaults.width = options.defaultColumnWidth;
242
243 columnsById = {};
244 for (var i = 0; i < columns.length; i++) {
245 var m = columns[i] = $.extend({}, columnDefaults, columns[i]);
246 columnsById[m.id] = i;
247 if (m.minWidth && m.width < m.minWidth) {
248 m.width = m.minWidth;
249 }
250 if (m.maxWidth && m.width > m.maxWidth) {
251 m.width = m.maxWidth;
252 }
253 }
254
255 // validate loaded JavaScript modules against requested options
256 if (options.enableColumnReorder && !$.fn.sortable) {
257 throw new Error("SlickGrid's 'enableColumnReorder = true' option requires jquery-ui.sortable module to be loaded");
258 }
259
260 editController = {
261 "commitCurrentEdit": commitCurrentEdit,
262 "cancelCurrentEdit": cancelCurrentEdit
263 };
264
265 $container
266 .empty()
267 .css("overflow", "hidden")
268 .css("outline", 0)
269 .addClass(uid)
270 .addClass("ui-widget");
271
272 // set up a positioning container if needed
273 if (!/relative|absolute|fixed/.test($container.css("position"))) {
274 $container.css("position", "relative");
275 }
276
277 $focusSink = $("<div tabIndex='0' hideFocus style='position:fixed;width:0;height:0;top:0;left:0;outline:0;'></div>").appendTo($container);
278
279 if (options.createPreHeaderPanel) {
280 $preHeaderPanelScroller = $("<div class='slick-preheader-panel ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
281 $preHeaderPanel = $("<div />").appendTo($preHeaderPanelScroller);
282 $preHeaderPanelSpacer = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
283 .appendTo($preHeaderPanelScroller);
284
285 if (!options.showPreHeaderPanel) {
286 $preHeaderPanelScroller.hide();
287 }
288 }
289
290 $headerScroller = $("<div class='slick-header ui-state-default' />").appendTo($container);
291 $headers = $("<div class='slick-header-columns' style='left:-1000px' />").appendTo($headerScroller);
292
293 $headerRowScroller = $("<div class='slick-headerrow ui-state-default' />").appendTo($container);
294 $headerRow = $("<div class='slick-headerrow-columns' />").appendTo($headerRowScroller);
295 $headerRowSpacer = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
296 .appendTo($headerRowScroller);
297
298 $topPanelScroller = $("<div class='slick-top-panel-scroller ui-state-default' />").appendTo($container);
299 $topPanel = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScroller);
300
301 if (!options.showTopPanel) {
302 $topPanelScroller.hide();
303 }
304
305 if (!options.showHeaderRow) {
306 $headerRowScroller.hide();
307 }
308
309 $viewport = $("<div class='slick-viewport' style='width:100%;overflow:auto;outline:0;position:relative;;'>").appendTo($container);
310 $viewport.css("overflow-y", options.alwaysShowVerticalScroll ? "scroll" : (options.autoHeight ? "hidden" : "auto"));
311 $viewport.css("overflow-x", options.forceFitColumns ? "hidden" : "auto");
312 if (options.viewportClass) $viewport.toggleClass(options.viewportClass, true);
313
314 $canvas = $("<div class='grid-canvas' />").appendTo($viewport);
315
316 scrollbarDimensions = scrollbarDimensions || measureScrollbar();
317
318 if ($preHeaderPanelSpacer) $preHeaderPanelSpacer.css("width", getCanvasWidth() + scrollbarDimensions.width + "px");
319 $headers.width(getHeadersWidth());
320 $headerRowSpacer.css("width", getCanvasWidth() + scrollbarDimensions.width + "px");
321
322
323
324 if (options.createFooterRow) {
325 $footerRowScroller = $("<div class='slick-footerrow ui-state-default' />").appendTo($container);
326 $footerRow = $("<div class='slick-footerrow-columns' />").appendTo($footerRowScroller);
327 $footerRowSpacer = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
328 .css("width", getCanvasWidth() + scrollbarDimensions.width + "px")
329 .appendTo($footerRowScroller);
330
331 if (!options.showFooterRow) {
332 $footerRowScroller.hide();
333 }
334 }
335
336 $focusSink2 = $focusSink.clone().appendTo($container);
337
338 if (!options.explicitInitialization) {
339 finishInitialization();
340 }
341 }
342
343 function finishInitialization() {
344 if (!initialized) {
345 initialized = true;
346
347 viewportW = parseFloat($.css($container[0], "width", true));
348
349 // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?)
350 // calculate the diff so we can set consistent sizes
351 measureCellPaddingAndBorder();
352
353 // for usability reasons, all text selection in SlickGrid is disabled
354 // with the exception of input and textarea elements (selection must
355 // be enabled there so that editors work as expected); note that
356 // selection in grid cells (grid body) is already unavailable in
357 // all browsers except IE
358 disableSelection($headers); // disable all text selection in header (including input and textarea)
359
360 if (!options.enableTextSelectionOnCells) {
361 // disable text selection in grid cells except in input and textarea elements
362 // (this is IE-specific, because selectstart event will only fire in IE)
363 $viewport.on("selectstart.ui", function (event) {
364 return $(event.target).is("input,textarea");
365 });
366 }
367
368 updateColumnCaches();
369 createColumnHeaders();
370 setupColumnSort();
371 createCssRules();
372 resizeCanvas();
373 bindAncestorScrollEvents();
374
375 $container
376 .on("resize.slickgrid", resizeCanvas);
377 $viewport
378 //.on("click", handleClick)
379 .on("scroll", handleScroll);
380 $headerScroller
381 //.on("scroll", handleHeaderScroll)
382 .on("contextmenu", handleHeaderContextMenu)
383 .on("click", handleHeaderClick)
384 .on("mouseenter", ".slick-header-column", handleHeaderMouseEnter)
385 .on("mouseleave", ".slick-header-column", handleHeaderMouseLeave);
386 $headerRowScroller
387 .on("scroll", handleHeaderRowScroll);
388
389 if (options.createFooterRow) {
390 $footerRowScroller
391 .on("scroll", handleFooterRowScroll);
392 }
393
394 if (options.createPreHeaderPanel) {
395 $preHeaderPanelScroller
396 .on("scroll", handlePreHeaderPanelScroll);
397 }
398
399 $focusSink.add($focusSink2)
400 .on("keydown", handleKeyDown);
401 $canvas
402 .on("keydown", handleKeyDown)
403 .on("click", handleClick)
404 .on("dblclick", handleDblClick)
405 .on("contextmenu", handleContextMenu)
406 .on("draginit", handleDragInit)
407 .on("dragstart", {distance: 3}, handleDragStart)
408 .on("drag", handleDrag)
409 .on("dragend", handleDragEnd)
410 .on("mouseenter", ".slick-cell", handleMouseEnter)
411 .on("mouseleave", ".slick-cell", handleMouseLeave);
412
413 // Work around http://crbug.com/312427.
414 if (navigator.userAgent.toLowerCase().match(/webkit/) &&
415 navigator.userAgent.toLowerCase().match(/macintosh/)) {
416 $canvas.on("mousewheel", handleMouseWheel);
417 }
418 restoreCssFromHiddenInit();
419 }
420 }
421
422 function cacheCssForHiddenInit() {
423 // handle display:none on container or container parents
424 $hiddenParents = $container.parents().addBack().not(':visible');
425 $hiddenParents.each(function() {
426 var old = {};
427 for ( var name in cssShow ) {
428 old[ name ] = this.style[ name ];
429 this.style[ name ] = cssShow[ name ];
430 }
431 oldProps.push(old);
432 });
433 }
434
435 function restoreCssFromHiddenInit() {
436 // finish handle display:none on container or container parents
437 // - put values back the way they were
438 $hiddenParents.each(function(i) {
439 var old = oldProps[i];
440 for ( var name in cssShow ) {
441 this.style[ name ] = old[ name ];
442 }
443 });
444 }
445
446 function registerPlugin(plugin) {
447 plugins.unshift(plugin);
448 plugin.init(self);
449 }
450
451 function unregisterPlugin(plugin) {
452 for (var i = plugins.length; i >= 0; i--) {
453 if (plugins[i] === plugin) {
454 if (plugins[i].destroy) {
455 plugins[i].destroy();
456 }
457 plugins.splice(i, 1);
458 break;
459 }
460 }
461 }
462
463 function setSelectionModel(model) {
464 if (selectionModel) {
465 selectionModel.onSelectedRangesChanged.unsubscribe(handleSelectedRangesChanged);
466 if (selectionModel.destroy) {
467 selectionModel.destroy();
468 }
469 }
470
471 selectionModel = model;
472 if (selectionModel) {
473 selectionModel.init(self);
474 selectionModel.onSelectedRangesChanged.subscribe(handleSelectedRangesChanged);
475 }
476 }
477
478 function getSelectionModel() {
479 return selectionModel;
480 }
481
482 function getCanvasNode() {
483 return $canvas[0];
484 }
485
486 function measureScrollbar() {
487 var $outerdiv = $('<div class="' + $viewport.className + '" style="position:absolute; top:-10000px; left:-10000px; overflow:auto; width:100px; height:100px;"></div>').appendTo($viewport);
488 var $innerdiv = $('<div style="width:200px; height:200px; overflow:auto;"></div>').appendTo($outerdiv);
489 var dim = {
490 width: $outerdiv[0].offsetWidth - $outerdiv[0].clientWidth,
491 height: $outerdiv[0].offsetHeight - $outerdiv[0].clientHeight
492 };
493 $innerdiv.remove();
494 $outerdiv.remove();
495 return dim;
496 }
497
498 function getColumnTotalWidth(includeScrollbar) {
499 var totalWidth = 0;
500 for (var i = 0, ii = columns.length; i < ii; i++) {
501 var width = columns[i].width;
502 totalWidth += width;
503 }
504 if (includeScrollbar) {
505 totalWidth += scrollbarDimensions.width;
506 }
507 return totalWidth;
508 }
509
510 function getHeadersWidth() {
511 var headersWidth = getColumnTotalWidth(!options.autoHeight);
512 return Math.max(headersWidth, viewportW) + 1000;
513 }
514
515 function getCanvasWidth() {
516 var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
517 var rowWidth = 0;
518 var i = columns.length;
519 while (i--) {
520 rowWidth += columns[i].width;
521 }
522 return options.fullWidthRows ? Math.max(rowWidth, availableWidth) : rowWidth;
523 }
524
525 function updateCanvasWidth(forceColumnWidthsUpdate) {
526 var oldCanvasWidth = canvasWidth;
527 canvasWidth = getCanvasWidth();
528
529 if (canvasWidth != oldCanvasWidth) {
530 $canvas.width(canvasWidth);
531 $headerRow.width(canvasWidth);
532 if (options.createFooterRow) { $footerRow.width(canvasWidth); }
533 if (options.createPreHeaderPanel) { $preHeaderPanel.width(canvasWidth); }
534 $headers.width(getHeadersWidth());
535 viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width);
536 }
537
538 var w=canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0);
539 $headerRowSpacer.width(w);
540 if (options.createFooterRow) { $footerRowSpacer.width(w); }
541 if (options.createPreHeaderPanel) { $preHeaderPanelSpacer.width(w); }
542
543 if (canvasWidth != oldCanvasWidth || forceColumnWidthsUpdate) {
544 applyColumnWidths();
545 }
546 }
547
548 function disableSelection($target) {
549 if ($target && $target.jquery) {
550 $target
551 .attr("unselectable", "on")
552 .css("MozUserSelect", "none")
553 .on("selectstart.ui", function () {
554 return false;
555 }); // from jquery:ui.core.js 1.7.2
556 }
557 }
558
559 function getMaxSupportedCssHeight() {
560 var supportedHeight = 1000000;
561 // FF reports the height back but still renders blank after ~6M px
562 var testUpTo = navigator.userAgent.toLowerCase().match(/firefox/) ? 6000000 : 1000000000;
563 var div = $("<div style='display:none' />").appendTo(document.body);
564
565 while (true) {
566 var test = supportedHeight * 2;
567 div.css("height", test);
568 if (test > testUpTo || div.height() !== test) {
569 break;
570 } else {
571 supportedHeight = test;
572 }
573 }
574
575 div.remove();
576 return supportedHeight;
577 }
578
579 function getUID() {
580 return uid;
581 }
582
583 function getHeaderColumnWidthDiff() {
584 return headerColumnWidthDiff;
585 }
586
587 function getScrollbarDimensions() {
588 return scrollbarDimensions;
589 }
590
591 // TODO: this is static. need to handle page mutation.
592 function bindAncestorScrollEvents() {
593 var elem = $canvas[0];
594 while ((elem = elem.parentNode) != document.body && elem != null) {
595 // bind to scroll containers only
596 if (elem == $viewport[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) {
597 var $elem = $(elem);
598 if (!$boundAncestors) {
599 $boundAncestors = $elem;
600 } else {
601 $boundAncestors = $boundAncestors.add($elem);
602 }
603 $elem.on("scroll." + uid, handleActiveCellPositionChange);
604 }
605 }
606 }
607
608 function unbindAncestorScrollEvents() {
609 if (!$boundAncestors) {
610 return;
611 }
612 $boundAncestors.off("scroll." + uid);
613 $boundAncestors = null;
614 }
615
616 function updateColumnHeader(columnId, title, toolTip) {
617 if (!initialized) { return; }
618 var idx = getColumnIndex(columnId);
619 if (idx == null) {
620 return;
621 }
622
623 var columnDef = columns[idx];
624 var $header = $headers.children().eq(idx);
625 if ($header) {
626 if (title !== undefined) {
627 columns[idx].name = title;
628 }
629 if (toolTip !== undefined) {
630 columns[idx].toolTip = toolTip;
631 }
632
633 trigger(self.onBeforeHeaderCellDestroy, {
634 "node": $header[0],
635 "column": columnDef,
636 "grid": self
637 });
638
639 $header
640 .attr("title", toolTip || "")
641 .children().eq(0).html(title);
642
643 trigger(self.onHeaderCellRendered, {
644 "node": $header[0],
645 "column": columnDef,
646 "grid": self
647 });
648 }
649 }
650
651 function getHeader() {
652 return $headers[0];
653 }
654
655 function getHeaderColumn(columnIdOrIdx) {
656 var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx));
657 var $rtn = $headers.children().eq(idx);
658 return $rtn && $rtn[0];
659 }
660
661 function getHeaderRow() {
662 return $headerRow[0];
663 }
664
665 function getFooterRow() {
666 return $footerRow[0];
667 }
668
669 function getPreHeaderPanel() {
670 return $preHeaderPanel[0];
671 }
672
673 function getHeaderRowColumn(columnIdOrIdx) {
674 var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx));
675 var $rtn = $headerRow.children().eq(idx);
676 return $rtn && $rtn[0];
677 }
678
679 function getFooterRowColumn(columnIdOrIdx) {
680 var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx));
681 var $rtn = $footerRow.children().eq(idx);
682 return $rtn && $rtn[0];
683 }
684
685 function createColumnHeaders() {
686 function onMouseEnter() {
687 $(this).addClass("ui-state-hover");
688 }
689
690 function onMouseLeave() {
691 $(this).removeClass("ui-state-hover");
692 }
693
694 $headers.find(".slick-header-column")
695 .each(function() {
696 var columnDef = $(this).data("column");
697 if (columnDef) {
698 trigger(self.onBeforeHeaderCellDestroy, {
699 "node": this,
700 "column": columnDef,
701 "grid": self
702 });
703 }
704 });
705 $headers.empty();
706 $headers.width(getHeadersWidth());
707
708 $headerRow.find(".slick-headerrow-column")
709 .each(function() {
710 var columnDef = $(this).data("column");
711 if (columnDef) {
712 trigger(self.onBeforeHeaderRowCellDestroy, {
713 "node": this,
714 "column": columnDef,
715 "grid": self
716 });
717 }
718 });
719 $headerRow.empty();
720
721 if (options.createFooterRow) {
722 $footerRow.find(".slick-footerrow-column")
723 .each(function() {
724 var columnDef = $(this).data("column");
725 if (columnDef) {
726 trigger(self.onBeforeFooterRowCellDestroy, {
727 "node": this,
728 "column": columnDef
729 });
730 }
731 });
732 $footerRow.empty();
733 }
734
735 for (var i = 0; i < columns.length; i++) {
736 var m = columns[i];
737
738 var header = $("<div class='ui-state-default slick-header-column' />")
739 .html("<span class='slick-column-name'>" + m.name + "</span>")
740 .width(m.width - headerColumnWidthDiff)
741 .attr("id", "" + uid + m.id)
742 .attr("title", m.toolTip || "")
743 .data("column", m)
744 .addClass(m.headerCssClass || "")
745 .appendTo($headers);
746
747 if (options.enableColumnReorder || m.sortable) {
748 header
749 .on('mouseenter', onMouseEnter)
750 .on('mouseleave', onMouseLeave);
751 }
752
753 if (m.sortable) {
754 header.addClass("slick-header-sortable");
755 header.append("<span class='slick-sort-indicator"
756 + (options.numberedMultiColumnSort && !options.sortColNumberInSeparateSpan ? " slick-sort-indicator-numbered" : "" ) + "' />");
757 if (options.numberedMultiColumnSort && options.sortColNumberInSeparateSpan) { header.append("<span class='slick-sort-indicator-numbered' />"); }
758 }
759
760 trigger(self.onHeaderCellRendered, {
761 "node": header[0],
762 "column": m,
763 "grid": self
764 });
765
766 if (options.showHeaderRow) {
767 var headerRowCell = $("<div class='ui-state-default slick-headerrow-column l" + i + " r" + i + "'></div>")
768 .data("column", m)
769 .appendTo($headerRow);
770
771 trigger(self.onHeaderRowCellRendered, {
772 "node": headerRowCell[0],
773 "column": m,
774 "grid": self
775 });
776 }
777 if (options.createFooterRow && options.showFooterRow) {
778 var footerRowCell = $("<div class='ui-state-default slick-footerrow-column l" + i + " r" + i + "'></div>")
779 .data("column", m)
780 .appendTo($footerRow);
781
782 trigger(self.onFooterRowCellRendered, {
783 "node": footerRowCell[0],
784 "column": m
785 });
786 }
787 }
788
789 setSortColumns(sortColumns);
790 setupColumnResize();
791 if (options.enableColumnReorder) {
792 if (typeof options.enableColumnReorder == 'function') {
793 options.enableColumnReorder(self, $headers, headerColumnWidthDiff, setColumns, setupColumnResize, columns, getColumnIndex, uid, trigger);
794 } else {
795 setupColumnReorder();
796 }
797 }
798 }
799
800 function setupColumnSort() {
801 $headers.click(function (e) {
802 if (columnResizeDragging) return;
803 // temporary workaround for a bug in jQuery 1.7.1 (http://bugs.jquery.com/ticket/11328)
804 e.metaKey = e.metaKey || e.ctrlKey;
805
806 if ($(e.target).hasClass("slick-resizable-handle")) {
807 return;
808 }
809
810 var $col = $(e.target).closest(".slick-header-column");
811 if (!$col.length) {
812 return;
813 }
814
815 var column = $col.data("column");
816 if (column.sortable) {
817 if (!getEditorLock().commitCurrentEdit()) {
818 return;
819 }
820
821 var sortColumn = null;
822 var i = 0;
823 for (; i < sortColumns.length; i++) {
824 if (sortColumns[i].columnId == column.id) {
825 sortColumn = sortColumns[i];
826 sortColumn.sortAsc = !sortColumn.sortAsc;
827 break;
828 }
829 }
830 var hadSortCol = !!sortColumn;
831
832 if (options.tristateMultiColumnSort) {
833 if (!sortColumn) {
834 sortColumn = { columnId: column.id, sortAsc: column.defaultSortAsc };
835 }
836 if (hadSortCol && sortColumn.sortAsc) {
837 // three state: remove sort rather than go back to ASC
838 sortColumns.splice(i, 1);
839 sortColumn = null;
840 }
841 if (!options.multiColumnSort) { sortColumns = []; }
842 if (sortColumn && (!hadSortCol || !options.multiColumnSort)) {
843 sortColumns.push(sortColumn);
844 }
845 } else {
846 // legacy behaviour
847 if (e.metaKey && options.multiColumnSort) {
848 if (sortColumn) {
849 sortColumns.splice(i, 1);
850 }
851 }
852 else {
853 if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) {
854 sortColumns = [];
855 }
856
857 if (!sortColumn) {
858 sortColumn = { columnId: column.id, sortAsc: column.defaultSortAsc };
859 sortColumns.push(sortColumn);
860 } else if (sortColumns.length == 0) {
861 sortColumns.push(sortColumn);
862 }
863 }
864 }
865
866 setSortColumns(sortColumns);
867
868 if (!options.multiColumnSort) {
869 trigger(self.onSort, {
870 multiColumnSort: false,
871 sortCol: (sortColumns.length > 0 ? column : null),
872 sortAsc: (sortColumns.length > 0 ? sortColumns[0].sortAsc : true)
873 }, e);
874 } else {
875 trigger(self.onSort, {
876 multiColumnSort: true,
877 sortCols: $.map(sortColumns, function(col) {
878 return {sortCol: columns[getColumnIndex(col.columnId)], sortAsc: col.sortAsc };
879 })
880 }, e);
881 }
882 }
883 });
884 }
885
886 function setupColumnReorder() {
887 $headers.filter(":ui-sortable").sortable("destroy");
888 $headers.sortable({
889 containment: "parent",
890 distance: 3,
891 axis: "x",
892 cursor: "default",
893 tolerance: "intersection",
894 helper: "clone",
895 placeholder: "slick-sortable-placeholder ui-state-default slick-header-column",
896 start: function (e, ui) {
897 ui.placeholder.width(ui.helper.outerWidth() - headerColumnWidthDiff);
898 $(ui.helper).addClass("slick-header-column-active");
899 },
900 beforeStop: function (e, ui) {
901 $(ui.helper).removeClass("slick-header-column-active");
902 },
903 stop: function (e) {
904 if (!getEditorLock().commitCurrentEdit()) {
905 $(this).sortable("cancel");
906 return;
907 }
908
909 var reorderedIds = $headers.sortable("toArray");
910 var reorderedColumns = [];
911 for (var i = 0; i < reorderedIds.length; i++) {
912 reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid, ""))]);
913 }
914 setColumns(reorderedColumns);
915
916 trigger(self.onColumnsReordered, {});
917 e.stopPropagation();
918 setupColumnResize();
919 }
920 });
921 }
922
923 function setupColumnResize() {
924 var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable;
925 columnElements = $headers.children();
926 columnElements.find(".slick-resizable-handle").remove();
927 columnElements.each(function (i, e) {
928 if (i >= columns.length) { return; }
929 if (columns[i].resizable) {
930 if (firstResizable === undefined) {
931 firstResizable = i;
932 }
933 lastResizable = i;
934 }
935 });
936 if (firstResizable === undefined) {
937 return;
938 }
939 columnElements.each(function (i, e) {
940 if (i >= columns.length) { return; }
941 if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) {
942 return;
943 }
944 $col = $(e);
945 $("<div class='slick-resizable-handle' />")
946 .appendTo(e)
947 .on("dragstart", function (e, dd) {
948 if (!getEditorLock().commitCurrentEdit()) {
949 return false;
950 }
951 pageX = e.pageX;
952 $(this).parent().addClass("slick-header-column-active");
953 var shrinkLeewayOnRight = null, stretchLeewayOnRight = null;
954 // lock each column's width option to current width
955 columnElements.each(function (i, e) {
956 if (i >= columns.length) { return; }
957 columns[i].previousWidth = $(e).outerWidth();
958 });
959 if (options.forceFitColumns) {
960 shrinkLeewayOnRight = 0;
961 stretchLeewayOnRight = 0;
962 // colums on right affect maxPageX/minPageX
963 for (j = i + 1; j < columns.length; j++) {
964 c = columns[j];
965 if (c.resizable) {
966 if (stretchLeewayOnRight !== null) {
967 if (c.maxWidth) {
968 stretchLeewayOnRight += c.maxWidth - c.previousWidth;
969 } else {
970 stretchLeewayOnRight = null;
971 }
972 }
973 shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
974 }
975 }
976 }
977 var shrinkLeewayOnLeft = 0, stretchLeewayOnLeft = 0;
978 for (j = 0; j <= i; j++) {
979 // columns on left only affect minPageX
980 c = columns[j];
981 if (c.resizable) {
982 if (stretchLeewayOnLeft !== null) {
983 if (c.maxWidth) {
984 stretchLeewayOnLeft += c.maxWidth - c.previousWidth;
985 } else {
986 stretchLeewayOnLeft = null;
987 }
988 }
989 shrinkLeewayOnLeft += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
990 }
991 }
992 if (shrinkLeewayOnRight === null) {
993 shrinkLeewayOnRight = 100000;
994 }
995 if (shrinkLeewayOnLeft === null) {
996 shrinkLeewayOnLeft = 100000;
997 }
998 if (stretchLeewayOnRight === null) {
999 stretchLeewayOnRight = 100000;
1000 }
1001 if (stretchLeewayOnLeft === null) {
1002 stretchLeewayOnLeft = 100000;
1003 }
1004 maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft);
1005 minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight);
1006 })
1007 .on("drag", function (e, dd) {
1008 columnResizeDragging = true;
1009 var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x;
1010 if (d < 0) { // shrink column
1011 x = d;
1012 for (j = i; j >= 0; j--) {
1013 c = columns[j];
1014 if (c.resizable) {
1015 actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
1016 if (x && c.previousWidth + x < actualMinWidth) {
1017 x += c.previousWidth - actualMinWidth;
1018 c.width = actualMinWidth;
1019 } else {
1020 c.width = c.previousWidth + x;
1021 x = 0;
1022 }
1023 }
1024 }
1025
1026 if (options.forceFitColumns) {
1027 x = -d;
1028 for (j = i + 1; j < columns.length; j++) {
1029 c = columns[j];
1030 if (c.resizable) {
1031 if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
1032 x -= c.maxWidth - c.previousWidth;
1033 c.width = c.maxWidth;
1034 } else {
1035 c.width = c.previousWidth + x;
1036 x = 0;
1037 }
1038 }
1039 }
1040 }
1041 } else { // stretch column
1042 x = d;
1043 for (j = i; j >= 0; j--) {
1044 c = columns[j];
1045 if (c.resizable) {
1046 if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
1047 x -= c.maxWidth - c.previousWidth;
1048 c.width = c.maxWidth;
1049 } else {
1050 c.width = c.previousWidth + x;
1051 x = 0;
1052 }
1053 }
1054 }
1055
1056 if (options.forceFitColumns) {
1057 x = -d;
1058 for (j = i + 1; j < columns.length; j++) {
1059 c = columns[j];
1060 if (c.resizable) {
1061 actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
1062 if (x && c.previousWidth + x < actualMinWidth) {
1063 x += c.previousWidth - actualMinWidth;
1064 c.width = actualMinWidth;
1065 } else {
1066 c.width = c.previousWidth + x;
1067 x = 0;
1068 }
1069 }
1070 }
1071 }
1072 }
1073 applyColumnHeaderWidths();
1074 if (options.syncColumnCellResize) {
1075 applyColumnWidths();
1076 }
1077 })
1078 .on("dragend", function (e, dd) {
1079 var newWidth;
1080 $(this).parent().removeClass("slick-header-column-active");
1081 for (j = 0; j < columns.length; j++) {
1082 c = columns[j];
1083 newWidth = $(columnElements[j]).outerWidth();
1084
1085 if (c.previousWidth !== newWidth && c.rerenderOnResize) {
1086 invalidateAllRows();
1087 }
1088 }
1089 updateCanvasWidth(true);
1090 render();
1091 trigger(self.onColumnsResized, {});
1092 setTimeout(function () { columnResizeDragging = false; }, 300);
1093 });
1094 });
1095 }
1096
1097 function getVBoxDelta($el) {
1098 var p = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];
1099 var delta = 0;
1100 $.each(p, function (n, val) {
1101 delta += parseFloat($el.css(val)) || 0;
1102 });
1103 return delta;
1104 }
1105
1106 function measureCellPaddingAndBorder() {
1107 var el;
1108 var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"];
1109 var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];
1110
1111 // jquery prior to version 1.8 handles .width setter/getter as a direct css write/read
1112 // jquery 1.8 changed .width to read the true inner element width if box-sizing is set to border-box, and introduced a setter for .outerWidth
1113 // so for equivalent functionality, prior to 1.8 use .width, and after use .outerWidth
1114 var verArray = $.fn.jquery.split('.');
1115 jQueryNewWidthBehaviour = (verArray[0]==1 && verArray[1]>=8) || verArray[0] >=2;
1116
1117 el = $("<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>").appendTo($headers);
1118 headerColumnWidthDiff = headerColumnHeightDiff = 0;
1119 if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") {
1120 $.each(h, function (n, val) {
1121 headerColumnWidthDiff += parseFloat(el.css(val)) || 0;
1122 });
1123 $.each(v, function (n, val) {
1124 headerColumnHeightDiff += parseFloat(el.css(val)) || 0;
1125 });
1126 }
1127 el.remove();
1128
1129 var r = $("<div class='slick-row' />").appendTo($canvas);
1130 el = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r);
1131 cellWidthDiff = cellHeightDiff = 0;
1132 if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") {
1133 $.each(h, function (n, val) {
1134 cellWidthDiff += parseFloat(el.css(val)) || 0;
1135 });
1136 $.each(v, function (n, val) {
1137 cellHeightDiff += parseFloat(el.css(val)) || 0;
1138 });
1139 }
1140 r.remove();
1141
1142 absoluteColumnMinWidth = Math.max(headerColumnWidthDiff, cellWidthDiff);
1143 }
1144
1145 function createCssRules() {
1146 $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head"));
1147 var rowHeight = (options.rowHeight - cellHeightDiff);
1148 var rules = [
1149 "." + uid + " .slick-header-column { left: 1000px; }",
1150 "." + uid + " .slick-top-panel { height:" + options.topPanelHeight + "px; }",
1151 "." + uid + " .slick-preheader-panel { height:" + options.preHeaderPanelHeight + "px; }",
1152 "." + uid + " .slick-headerrow-columns { height:" + options.headerRowHeight + "px; }",
1153 "." + uid + " .slick-footerrow-columns { height:" + options.footerRowHeight + "px; }",
1154 "." + uid + " .slick-cell { height:" + rowHeight + "px; }",
1155 "." + uid + " .slick-row { height:" + options.rowHeight + "px; }"
1156 ];
1157
1158 for (var i = 0; i < columns.length; i++) {
1159 rules.push("." + uid + " .l" + i + " { }");
1160 rules.push("." + uid + " .r" + i + " { }");
1161 }
1162
1163 if ($style[0].styleSheet) { // IE
1164 $style[0].styleSheet.cssText = rules.join(" ");
1165 } else {
1166 $style[0].appendChild(document.createTextNode(rules.join(" ")));
1167 }
1168 }
1169
1170 function getColumnCssRules(idx) {
1171 var i;
1172 if (!stylesheet) {
1173 var sheets = document.styleSheets;
1174 for (i = 0; i < sheets.length; i++) {
1175 if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
1176 stylesheet = sheets[i];
1177 break;
1178 }
1179 }
1180
1181 if (!stylesheet) {
1182 throw new Error("Cannot find stylesheet.");
1183 }
1184
1185 // find and cache column CSS rules
1186 columnCssRulesL = [];
1187 columnCssRulesR = [];
1188 var cssRules = (stylesheet.cssRules || stylesheet.rules);
1189 var matches, columnIdx;
1190 for (i = 0; i < cssRules.length; i++) {
1191 var selector = cssRules[i].selectorText;
1192 if (matches = /\.l\d+/.exec(selector)) {
1193 columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
1194 columnCssRulesL[columnIdx] = cssRules[i];
1195 } else if (matches = /\.r\d+/.exec(selector)) {
1196 columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
1197 columnCssRulesR[columnIdx] = cssRules[i];
1198 }
1199 }
1200 }
1201
1202 return {
1203 "left": columnCssRulesL[idx],
1204 "right": columnCssRulesR[idx]
1205 };
1206 }
1207
1208 function removeCssRules() {
1209 $style.remove();
1210 stylesheet = null;
1211 }
1212
1213 function destroy() {
1214 getEditorLock().cancelCurrentEdit();
1215
1216 trigger(self.onBeforeDestroy, {});
1217
1218 var i = plugins.length;
1219 while(i--) {
1220 unregisterPlugin(plugins[i]);
1221 }
1222
1223 if (options.enableColumnReorder) {
1224 $headers.filter(":ui-sortable").sortable("destroy");
1225 }
1226
1227 unbindAncestorScrollEvents();
1228 $container.off(".slickgrid");
1229 removeCssRules();
1230
1231 $canvas.off("draginit dragstart dragend drag");
1232 $container.empty().removeClass(uid);
1233 }
1234
1235
1236 //////////////////////////////////////////////////////////////////////////////////////////////
1237 // General
1238
1239 function trigger(evt, args, e) {
1240 e = e || new Slick.EventData();
1241 args = args || {};
1242 args.grid = self;
1243 return evt.notify(args, e, self);
1244 }
1245
1246 function getEditorLock() {
1247 return options.editorLock;
1248 }
1249
1250 function getEditController() {
1251 return editController;
1252 }
1253
1254 function getColumnIndex(id) {
1255 return columnsById[id];
1256 }
1257
1258 function autosizeColumns() {
1259 var i, c,
1260 widths = [],
1261 shrinkLeeway = 0,
1262 total = 0,
1263 prevTotal,
1264 availWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
1265
1266 for (i = 0; i < columns.length; i++) {
1267 c = columns[i];
1268 widths.push(c.width);
1269 total += c.width;
1270 if (c.resizable) {
1271 shrinkLeeway += c.width - Math.max(c.minWidth, absoluteColumnMinWidth);
1272 }
1273 }
1274
1275 // shrink
1276 prevTotal = total;
1277 while (total > availWidth && shrinkLeeway) {
1278 var shrinkProportion = (total - availWidth) / shrinkLeeway;
1279 for (i = 0; i < columns.length && total > availWidth; i++) {
1280 c = columns[i];
1281 var width = widths[i];
1282 if (!c.resizable || width <= c.minWidth || width <= absoluteColumnMinWidth) {
1283 continue;
1284 }
1285 var absMinWidth = Math.max(c.minWidth, absoluteColumnMinWidth);
1286 var shrinkSize = Math.floor(shrinkProportion * (width - absMinWidth)) || 1;
1287 shrinkSize = Math.min(shrinkSize, width - absMinWidth);
1288 total -= shrinkSize;
1289 shrinkLeeway -= shrinkSize;
1290 widths[i] -= shrinkSize;
1291 }
1292 if (prevTotal <= total) { // avoid infinite loop
1293 break;
1294 }
1295 prevTotal = total;
1296 }
1297
1298 // grow
1299 prevTotal = total;
1300 while (total < availWidth) {
1301 var growProportion = availWidth / total;
1302 for (i = 0; i < columns.length && total < availWidth; i++) {
1303 c = columns[i];
1304 var currentWidth = widths[i];
1305 var growSize;
1306
1307 if (!c.resizable || c.maxWidth <= currentWidth) {
1308 growSize = 0;
1309 } else {
1310 growSize = Math.min(Math.floor(growProportion * currentWidth) - currentWidth, (c.maxWidth - currentWidth) || 1000000) || 1;
1311 }
1312 total += growSize;
1313 widths[i] += (total <= availWidth ? growSize : 0);
1314 }
1315 if (prevTotal >= total) { // avoid infinite loop
1316 break;
1317 }
1318 prevTotal = total;
1319 }
1320
1321 var reRender = false;
1322 for (i = 0; i < columns.length; i++) {
1323 if (columns[i].rerenderOnResize && columns[i].width != widths[i]) {
1324 reRender = true;
1325 }
1326 columns[i].width = widths[i];
1327 }
1328
1329 applyColumnHeaderWidths();
1330 updateCanvasWidth(true);
1331
1332 trigger(self.onAutosizeColumns, { "columns": columns});
1333
1334 if (reRender) {
1335 invalidateAllRows();
1336 render();
1337 }
1338 }
1339
1340 function applyColumnHeaderWidths() {
1341 if (!initialized) { return; }
1342 var h;
1343
1344 for (var i = 0, headers = $headers.children(), ii = columns.length; i < ii; i++) {
1345 h = $(headers[i]);
1346 if (jQueryNewWidthBehaviour) {
1347 if (h.outerWidth() !== columns[i].width) {
1348 h.outerWidth(columns[i].width);
1349 }
1350 } else {
1351 if (h.width() !== columns[i].width - headerColumnWidthDiff) {
1352 h.width(columns[i].width - headerColumnWidthDiff);
1353 }
1354 }
1355 }
1356
1357 updateColumnCaches();
1358 }
1359
1360 function applyColumnWidths() {
1361 var x = 0, w, rule;
1362 for (var i = 0; i < columns.length; i++) {
1363 w = columns[i].width;
1364
1365 rule = getColumnCssRules(i);
1366 rule.left.style.left = x + "px";
1367 rule.right.style.right = (canvasWidth - x - w) + "px";
1368
1369 x += columns[i].width;
1370 }
1371 }
1372
1373 function setSortColumn(columnId, ascending) {
1374 setSortColumns([{ columnId: columnId, sortAsc: ascending}]);
1375 }
1376
1377 function setSortColumns(cols) {
1378 sortColumns = cols;
1379 var numberCols = options.numberedMultiColumnSort && sortColumns.length > 1;
1380 var headerColumnEls = $headers.children();
1381 headerColumnEls
1382 .removeClass("slick-header-column-sorted")
1383 .find(".slick-sort-indicator")
1384 .removeClass("slick-sort-indicator-asc slick-sort-indicator-desc");
1385 headerColumnEls
1386 .find(".slick-sort-indicator-numbered")
1387 .text('');
1388
1389 $.each(sortColumns, function(i, col) {
1390 if (col.sortAsc == null) {
1391 col.sortAsc = true;
1392 }
1393 var columnIndex = getColumnIndex(col.columnId);
1394 if (columnIndex != null) {
1395 headerColumnEls.eq(columnIndex)
1396 .addClass("slick-header-column-sorted")
1397 .find(".slick-sort-indicator")
1398 .addClass(col.sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc");
1399 if (numberCols) {
1400 headerColumnEls.eq(columnIndex)
1401 .find(".slick-sort-indicator-numbered")
1402 .text(i+1);
1403 }
1404 }
1405 });
1406 }
1407
1408 function getSortColumns() {
1409 return sortColumns;
1410 }
1411
1412 function handleSelectedRangesChanged(e, ranges) {
1413 selectedRows = [];
1414 var hash = {};
1415 for (var i = 0; i < ranges.length; i++) {
1416 for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
1417 if (!hash[j]) { // prevent duplicates
1418 selectedRows.push(j);
1419 hash[j] = {};
1420 }
1421 for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) {
1422 if (canCellBeSelected(j, k)) {
1423 hash[j][columns[k].id] = options.selectedCellCssClass;
1424 }
1425 }
1426 }
1427 }
1428
1429 setCellCssStyles(options.selectedCellCssClass, hash);
1430
1431 trigger(self.onSelectedRowsChanged, {rows: getSelectedRows()}, e);
1432 }
1433
1434 function getColumns() {
1435 return columns;
1436 }
1437
1438 function updateColumnCaches() {
1439 // Pre-calculate cell boundaries.
1440 columnPosLeft = [];
1441 columnPosRight = [];
1442 var x = 0;
1443 for (var i = 0, ii = columns.length; i < ii; i++) {
1444 columnPosLeft[i] = x;
1445 columnPosRight[i] = x + columns[i].width;
1446 x += columns[i].width;
1447 }
1448 }
1449
1450 function setColumns(columnDefinitions) {
1451 columns = columnDefinitions;
1452
1453 columnsById = {};
1454 for (var i = 0; i < columns.length; i++) {
1455 var m = columns[i] = $.extend({}, columnDefaults, columns[i]);
1456 columnsById[m.id] = i;
1457 if (m.minWidth && m.width < m.minWidth) {
1458 m.width = m.minWidth;
1459 }
1460 if (m.maxWidth && m.width > m.maxWidth) {
1461 m.width = m.maxWidth;
1462 }
1463 }
1464
1465 updateColumnCaches();
1466
1467 if (initialized) {
1468 invalidateAllRows();
1469 createColumnHeaders();
1470 removeCssRules();
1471 createCssRules();
1472 resizeCanvas();
1473 applyColumnWidths();
1474 handleScroll();
1475 }
1476 }
1477
1478 function getOptions() {
1479 return options;
1480 }
1481
1482 function setOptions(args, suppressRender) {
1483 if (!getEditorLock().commitCurrentEdit()) {
1484 return;
1485 }
1486
1487 makeActiveCellNormal();
1488
1489 if (options.enableAddRow !== args.enableAddRow) {
1490 invalidateRow(getDataLength());
1491 }
1492
1493 options = $.extend(options, args);
1494 validateAndEnforceOptions();
1495
1496 $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");
1497 if (!suppressRender) { render(); }
1498 }
1499
1500 function validateAndEnforceOptions() {
1501 if (options.autoHeight) {
1502 options.leaveSpaceForNewRows = false;
1503 }
1504 }
1505
1506 function setData(newData, scrollToTop) {
1507 data = newData;
1508 invalidateAllRows();
1509 updateRowCount();
1510 if (scrollToTop) {
1511 scrollTo(0);
1512 }
1513 }
1514
1515 function getData() {
1516 return data;
1517 }
1518
1519 function getDataLength() {
1520 if (data.getLength) {
1521 return data.getLength();
1522 } else {
1523 return data.length;
1524 }
1525 }
1526
1527 function getDataLengthIncludingAddNew() {
1528 return getDataLength() + (!options.enableAddRow ? 0
1529 : (!pagingActive || pagingIsLastPage ? 1 : 0)
1530 );
1531 }
1532
1533 function getDataItem(i) {
1534 if (data.getItem) {
1535 return data.getItem(i);
1536 } else {
1537 return data[i];
1538 }
1539 }
1540
1541 function getTopPanel() {
1542 return $topPanel[0];
1543 }
1544
1545 function setTopPanelVisibility(visible) {
1546 if (options.showTopPanel != visible) {
1547 options.showTopPanel = visible;
1548 if (visible) {
1549 $topPanelScroller.slideDown("fast", resizeCanvas);
1550 } else {
1551 $topPanelScroller.slideUp("fast", resizeCanvas);
1552 }
1553 }
1554 }
1555
1556 function setHeaderRowVisibility(visible) {
1557 if (options.showHeaderRow != visible) {
1558 options.showHeaderRow = visible;
1559 if (visible) {
1560 $headerRowScroller.slideDown("fast", resizeCanvas);
1561 } else {
1562 $headerRowScroller.slideUp("fast", resizeCanvas);
1563 }
1564 }
1565 }
1566
1567 function setFooterRowVisibility(visible) {
1568 if (options.showFooterRow != visible) {
1569 options.showFooterRow = visible;
1570 if (visible) {
1571 $footerRowScroller.slideDown("fast", resizeCanvas);
1572 } else {
1573 $footerRowScroller.slideUp("fast", resizeCanvas);
1574 }
1575 }
1576 }
1577
1578 function setPreHeaderPanelVisibility(visible) {
1579 if (options.showPreHeaderPanel != visible) {
1580 options.showPreHeaderPanel = visible;
1581 if (visible) {
1582 $preHeaderPanelScroller.slideDown("fast", resizeCanvas);
1583 } else {
1584 $preHeaderPanelScroller.slideUp("fast", resizeCanvas);
1585 }
1586 }
1587 }
1588
1589 function getContainerNode() {
1590 return $container.get(0);
1591 }
1592
1593 //////////////////////////////////////////////////////////////////////////////////////////////
1594 // Rendering / Scrolling
1595
1596 function getRowTop(row) {
1597 return options.rowHeight * row - offset;
1598 }
1599
1600 function getRowFromPosition(y) {
1601 return Math.floor((y + offset) / options.rowHeight);
1602 }
1603
1604 function scrollTo(y) {
1605 y = Math.max(y, 0);
1606 y = Math.min(y, th - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0));
1607
1608 var oldOffset = offset;
1609
1610 page = Math.min(n - 1, Math.floor(y / ph));
1611 offset = Math.round(page * cj);
1612 var newScrollTop = y - offset;
1613
1614 if (offset != oldOffset) {
1615 var range = getVisibleRange(newScrollTop);
1616 cleanupRows(range);
1617 updateRowPositions();
1618 }
1619
1620 if (prevScrollTop != newScrollTop) {
1621 vScrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1;
1622 $viewport[0].scrollTop = (lastRenderedScrollTop = scrollTop = prevScrollTop = newScrollTop);
1623
1624 trigger(self.onViewportChanged, {});
1625 }
1626 }
1627
1628 function defaultFormatter(row, cell, value, columnDef, dataContext, grid) {
1629 if (value == null) {
1630 return "";
1631 } else {
1632 return (value + "").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
1633 }
1634 }
1635
1636 function getFormatter(row, column) {
1637 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
1638
1639 // look up by id, then index
1640 var columnOverrides = rowMetadata &&
1641 rowMetadata.columns &&
1642 (rowMetadata.columns[column.id] || rowMetadata.columns[getColumnIndex(column.id)]);
1643
1644 return (columnOverrides && columnOverrides.formatter) ||
1645 (rowMetadata && rowMetadata.formatter) ||
1646 column.formatter ||
1647 (options.formatterFactory && options.formatterFactory.getFormatter(column)) ||
1648 options.defaultFormatter;
1649 }
1650
1651 function getEditor(row, cell) {
1652 var column = columns[cell];
1653 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
1654 var columnMetadata = rowMetadata && rowMetadata.columns;
1655
1656 if (columnMetadata && columnMetadata[column.id] && columnMetadata[column.id].editor !== undefined) {
1657 return columnMetadata[column.id].editor;
1658 }
1659 if (columnMetadata && columnMetadata[cell] && columnMetadata[cell].editor !== undefined) {
1660 return columnMetadata[cell].editor;
1661 }
1662
1663 return column.editor || (options.editorFactory && options.editorFactory.getEditor(column));
1664 }
1665
1666 function getDataItemValueForColumn(item, columnDef) {
1667 if (options.dataItemColumnValueExtractor) {
1668 return options.dataItemColumnValueExtractor(item, columnDef);
1669 }
1670 return item[columnDef.field];
1671 }
1672
1673 function appendRowHtml(stringArray, row, range, dataLength) {
1674 var d = getDataItem(row);
1675 var dataLoading = row < dataLength && !d;
1676 var rowCss = "slick-row" +
1677 (dataLoading ? " loading" : "") +
1678 (row === activeRow && options.showCellSelection ? " active" : "") +
1679 (row % 2 == 1 ? " odd" : " even");
1680
1681 if (!d) {
1682 rowCss += " " + options.addNewRowCssClass;
1683 }
1684
1685 var metadata = data.getItemMetadata && data.getItemMetadata(row);
1686
1687 if (metadata && metadata.cssClasses) {
1688 rowCss += " " + metadata.cssClasses;
1689 }
1690
1691 stringArray.push("<div class='ui-widget-content " + rowCss + "' style='top:" + getRowTop(row) + "px'>");
1692
1693 var colspan, m;
1694 for (var i = 0, ii = columns.length; i < ii; i++) {
1695 m = columns[i];
1696 colspan = 1;
1697 if (metadata && metadata.columns) {
1698 var columnData = metadata.columns[m.id] || metadata.columns[i];
1699 colspan = (columnData && columnData.colspan) || 1;
1700 if (colspan === "*") {
1701 colspan = ii - i;
1702 }
1703 }
1704
1705 // Do not render cells outside of the viewport.
1706 if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {
1707 if (columnPosLeft[i] > range.rightPx) {
1708 // All columns to the right are outside the range.
1709 break;
1710 }
1711
1712 appendCellHtml(stringArray, row, i, colspan, d);
1713 }
1714
1715 if (colspan > 1) {
1716 i += (colspan - 1);
1717 }
1718 }
1719
1720 stringArray.push("</div>");
1721 }
1722
1723 function appendCellHtml(stringArray, row, cell, colspan, item) {
1724 // stringArray: stringBuilder containing the HTML parts
1725 // row, cell: row and column index
1726 // colspan: HTML colspan
1727 // item: grid data for row
1728
1729 var m = columns[cell];
1730 var cellCss = "slick-cell l" + cell + " r" + Math.min(columns.length - 1, cell + colspan - 1) +
1731 (m.cssClass ? " " + m.cssClass : "");
1732 if (row === activeRow && cell === activeCell && options.showCellSelection) {
1733 cellCss += (" active");
1734 }
1735
1736 // TODO: merge them together in the setter
1737 for (var key in cellCssClasses) {
1738 if (cellCssClasses[key][row] && cellCssClasses[key][row][m.id]) {
1739 cellCss += (" " + cellCssClasses[key][row][m.id]);
1740 }
1741 }
1742
1743 var value = null, formatterResult = '';
1744 if (item) {
1745 value = getDataItemValueForColumn(item, m);
1746 formatterResult = getFormatter(row, m)(row, cell, value, m, item, self);
1747 if (formatterResult === null || formatterResult === undefined) { formatterResult = ''; }
1748 }
1749
1750 // get addl css class names from object type formatter return and from string type return of onBeforeAppendCell
1751 var addlCssClasses = trigger(self.onBeforeAppendCell, { row: row, cell: cell, value: value, dataContext: item }) || '';
1752 addlCssClasses += (formatterResult && formatterResult.addClasses ? (addlCssClasses ? ' ' : '') + formatterResult.addClasses : '');
1753
1754 stringArray.push("<div class='" + cellCss + (addlCssClasses ? ' ' + addlCssClasses : '') + "'>");
1755
1756 // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet)
1757 if (item) {
1758 stringArray.push(Object.prototype.toString.call(formatterResult) !== '[object Object]' ? formatterResult : formatterResult.text);
1759 }
1760
1761 stringArray.push("</div>");
1762
1763 rowsCache[row].cellRenderQueue.push(cell);
1764 rowsCache[row].cellColSpans[cell] = colspan;
1765 }
1766
1767
1768 function cleanupRows(rangeToKeep) {
1769 for (var i in rowsCache) {
1770 if (((i = parseInt(i, 10)) !== activeRow) && (i < rangeToKeep.top || i > rangeToKeep.bottom)) {
1771 removeRowFromCache(i);
1772 }
1773 }
1774 if (options.enableAsyncPostRenderCleanup) { startPostProcessingCleanup(); }
1775 }
1776
1777 function invalidate() {
1778 updateRowCount();
1779 invalidateAllRows();
1780 render();
1781 }
1782
1783 function invalidateAllRows() {
1784 if (currentEditor) {
1785 makeActiveCellNormal();
1786 }
1787 for (var row in rowsCache) {
1788 removeRowFromCache(row);
1789 }
1790 if (options.enableAsyncPostRenderCleanup) { startPostProcessingCleanup(); }
1791 }
1792
1793 function queuePostProcessedRowForCleanup(cacheEntry, postProcessedRow, rowIdx) {
1794 postProcessgroupId++;
1795
1796 // store and detach node for later async cleanup
1797 for (var columnIdx in postProcessedRow) {
1798 if (postProcessedRow.hasOwnProperty(columnIdx)) {
1799 postProcessedCleanupQueue.push({
1800 actionType: 'C',
1801 groupId: postProcessgroupId,
1802 node: cacheEntry.cellNodesByColumnIdx[ columnIdx | 0],
1803 columnIdx: columnIdx | 0,
1804 rowIdx: rowIdx
1805 });
1806 }
1807 }
1808 postProcessedCleanupQueue.push({
1809 actionType: 'R',
1810 groupId: postProcessgroupId,
1811 node: cacheEntry.rowNode
1812 });
1813 $(cacheEntry.rowNode).detach();
1814 }
1815
1816 function queuePostProcessedCellForCleanup(cellnode, columnIdx, rowIdx) {
1817 postProcessedCleanupQueue.push({
1818 actionType: 'C',
1819 groupId: postProcessgroupId,
1820 node: cellnode,
1821 columnIdx: columnIdx,
1822 rowIdx: rowIdx
1823 });
1824 $(cellnode).detach();
1825 }
1826
1827 function removeRowFromCache(row) {
1828 var cacheEntry = rowsCache[row];
1829 if (!cacheEntry) {
1830 return;
1831 }
1832
1833 if (cacheEntry.rowNode) {
1834 if (rowNodeFromLastMouseWheelEvent === cacheEntry.rowNode) {
1835 cacheEntry.rowNode.style.display = 'none';
1836 zombieRowNodeFromLastMouseWheelEvent = rowNodeFromLastMouseWheelEvent;
1837 zombieRowCacheFromLastMouseWheelEvent = cacheEntry;
1838 zombieRowPostProcessedFromLastMouseWheelEvent = postProcessedRows[row];
1839 // ignore post processing cleanup in this case - it will be dealt with later
1840 } else {
1841 if (options.enableAsyncPostRenderCleanup && postProcessedRows[row]) {
1842 queuePostProcessedRowForCleanup(cacheEntry, postProcessedRows[row], row);
1843 } else {
1844 $canvas[0].removeChild(cacheEntry.rowNode);
1845 }
1846 }
1847 }
1848
1849 delete rowsCache[row];
1850 delete postProcessedRows[row];
1851 renderedRows--;
1852 counter_rows_removed++;
1853 }
1854
1855 function invalidateRows(rows) {
1856 var i, rl;
1857 if (!rows || !rows.length) {
1858 return;
1859 }
1860 vScrollDir = 0;
1861 rl = rows.length;
1862 for (i = 0; i < rl; i++) {
1863 if (currentEditor && activeRow === rows[i]) {
1864 makeActiveCellNormal();
1865 }
1866 if (rowsCache[rows[i]]) {
1867 removeRowFromCache(rows[i]);
1868 }
1869 }
1870 if (options.enableAsyncPostRenderCleanup) { startPostProcessingCleanup(); }
1871 }
1872
1873 function invalidateRow(row) {
1874 if (!row && row !== 0) { return; }
1875 invalidateRows([row]);
1876 }
1877
1878 function applyFormatResultToCellNode(formatterResult, cellNode, suppressRemove) {
1879 if (formatterResult === null || formatterResult === undefined) { formatterResult = ''; }
1880 if (Object.prototype.toString.call(formatterResult) !== '[object Object]') {
1881 cellNode.innerHTML = formatterResult;
1882 return;
1883 }
1884 cellNode.innerHTML = formatterResult.text;
1885 if (formatterResult.removeClasses && !suppressRemove) {
1886 $(cellNode).removeClass(formatterResult.removeClasses);
1887 }
1888 if (formatterResult.addClasses) {
1889 $(cellNode).addClass(formatterResult.addClasses);
1890 }
1891 }
1892
1893 function updateCell(row, cell) {
1894 var cellNode = getCellNode(row, cell);
1895 if (!cellNode) {
1896 return;
1897 }
1898
1899 var m = columns[cell], d = getDataItem(row);
1900 if (currentEditor && activeRow === row && activeCell === cell) {
1901 currentEditor.loadValue(d);
1902 } else {
1903 var formatterResult = d ? getFormatter(row, m)(row, cell, getDataItemValueForColumn(d, m), m, d, self) : "";
1904 applyFormatResultToCellNode(formatterResult, cellNode);
1905 invalidatePostProcessingResults(row);
1906 }
1907 }
1908
1909 function updateRow(row) {
1910 var cacheEntry = rowsCache[row];
1911 if (!cacheEntry) {
1912 return;
1913 }
1914
1915 ensureCellNodesInRowsCache(row);
1916
1917 var formatterResult, d = getDataItem(row);
1918
1919 for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
1920 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
1921 continue;
1922 }
1923
1924 columnIdx = columnIdx | 0;
1925 var m = columns[columnIdx],
1926 node = cacheEntry.cellNodesByColumnIdx[columnIdx];
1927
1928 if (row === activeRow && columnIdx === activeCell && currentEditor) {
1929 currentEditor.loadValue(d);
1930 } else if (d) {
1931 formatterResult = getFormatter(row, m)(row, columnIdx, getDataItemValueForColumn(d, m), m, d, self);
1932 applyFormatResultToCellNode(formatterResult, node);
1933 } else {
1934 node.innerHTML = "";
1935 }
1936 }
1937
1938 invalidatePostProcessingResults(row);
1939 }
1940
1941 function getViewportHeight() {
1942 return parseFloat($.css($container[0], "height", true)) -
1943 parseFloat($.css($container[0], "paddingTop", true)) -
1944 parseFloat($.css($container[0], "paddingBottom", true)) -
1945 parseFloat($.css($headerScroller[0], "height")) - getVBoxDelta($headerScroller) -
1946 (options.showTopPanel ? options.topPanelHeight + getVBoxDelta($topPanelScroller) : 0) -
1947 (options.showHeaderRow ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0) -
1948 (options.createFooterRow && options.showFooterRow ? options.footerRowHeight + getVBoxDelta($footerRowScroller) : 0) -
1949 (options.createPreHeaderPanel && options.showPreHeaderPanel ? options.preHeaderPanelHeight + getVBoxDelta($preHeaderPanelScroller) : 0);
1950 }
1951
1952 function resizeCanvas() {
1953 if (!initialized) { return; }
1954 if (options.autoHeight) {
1955 viewportH = options.rowHeight * getDataLengthIncludingAddNew();
1956 } else {
1957 viewportH = getViewportHeight();
1958 }
1959
1960 numVisibleRows = Math.ceil(viewportH / options.rowHeight);
1961 viewportW = parseFloat($.css($container[0], "width", true));
1962 if (!options.autoHeight) {
1963 $viewport.height(viewportH);
1964 }
1965
1966 if (!scrollbarDimensions || !scrollbarDimensions.width) {
1967 scrollbarDimensions = measureScrollbar();
1968 }
1969
1970 if (options.forceFitColumns) {
1971 autosizeColumns();
1972 }
1973
1974 updateRowCount();
1975 handleScroll();
1976 // Since the width has changed, force the render() to reevaluate virtually rendered cells.
1977 lastRenderedScrollLeft = -1;
1978 render();
1979 }
1980
1981 function updatePagingStatusFromView( pagingInfo ) {
1982 pagingActive = (pagingInfo.pageSize !== 0);
1983 pagingIsLastPage = (pagingInfo.pageNum == pagingInfo.totalPages - 1);
1984 }
1985
1986 function updateRowCount() {
1987 if (!initialized) { return; }
1988
1989 var dataLength = getDataLength();
1990 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew();
1991 var numberOfRows = dataLengthIncludingAddNew +
1992 (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0);
1993
1994 var oldViewportHasVScroll = viewportHasVScroll;
1995 // with autoHeight, we do not need to accommodate the vertical scroll bar
1996 viewportHasVScroll = options.alwaysShowVerticalScroll || !options.autoHeight && (numberOfRows * options.rowHeight > viewportH);
1997 viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width);
1998
1999 makeActiveCellNormal();
2000
2001 // remove the rows that are now outside of the data range
2002 // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows
2003 var r1 = dataLength - 1;
2004 for (var i in rowsCache) {
2005 if (i > r1) {
2006 removeRowFromCache(i);
2007 }
2008 }
2009 if (options.enableAsyncPostRenderCleanup) { startPostProcessingCleanup(); }
2010
2011 if (activeCellNode && activeRow > r1) {
2012 resetActiveCell();
2013 }
2014
2015 var oldH = h;
2016 th = Math.max(options.rowHeight * numberOfRows, viewportH - scrollbarDimensions.height);
2017 if (th < maxSupportedCssHeight) {
2018 // just one page
2019 h = ph = th;
2020 n = 1;
2021 cj = 0;
2022 } else {
2023 // break into pages
2024 h = maxSupportedCssHeight;
2025 ph = h / 100;
2026 n = Math.floor(th / ph);
2027 cj = (th - h) / (n - 1);
2028 }
2029
2030 if (h !== oldH) {
2031 $canvas.css("height", h);
2032 scrollTop = $viewport[0].scrollTop;
2033 }
2034
2035 var oldScrollTopInRange = (scrollTop + offset <= th - viewportH);
2036
2037 if (th == 0 || scrollTop == 0) {
2038 page = offset = 0;
2039 } else if (oldScrollTopInRange) {
2040 // maintain virtual position
2041 scrollTo(scrollTop + offset);
2042 } else {
2043 // scroll to bottom
2044 scrollTo(th - viewportH);
2045 }
2046
2047 if (h != oldH && options.autoHeight) {
2048 resizeCanvas();
2049 }
2050
2051 if (options.forceFitColumns && oldViewportHasVScroll != viewportHasVScroll) {
2052 autosizeColumns();
2053 }
2054 updateCanvasWidth(false);
2055 }
2056
2057 function getVisibleRange(viewportTop, viewportLeft) {
2058 if (viewportTop == null) {
2059 viewportTop = scrollTop;
2060 }
2061 if (viewportLeft == null) {
2062 viewportLeft = scrollLeft;
2063 }
2064
2065 return {
2066 top: getRowFromPosition(viewportTop),
2067 bottom: getRowFromPosition(viewportTop + viewportH) + 1,
2068 leftPx: viewportLeft,
2069 rightPx: viewportLeft + viewportW
2070 };
2071 }
2072
2073 function getRenderedRange(viewportTop, viewportLeft) {
2074 var range = getVisibleRange(viewportTop, viewportLeft);
2075 var buffer = Math.round(viewportH / options.rowHeight);
2076 var minBuffer = options.minRowBuffer;
2077
2078 if (vScrollDir == -1) {
2079 range.top -= buffer;
2080 range.bottom += minBuffer;
2081 } else if (vScrollDir == 1) {
2082 range.top -= minBuffer;
2083 range.bottom += buffer;
2084 } else {
2085 range.top -= minBuffer;
2086 range.bottom += minBuffer;
2087 }
2088
2089 range.top = Math.max(0, range.top);
2090 range.bottom = Math.min(getDataLengthIncludingAddNew() - 1, range.bottom);
2091
2092 range.leftPx -= viewportW;
2093 range.rightPx += viewportW;
2094
2095 range.leftPx = Math.max(0, range.leftPx);
2096 range.rightPx = Math.min(canvasWidth, range.rightPx);
2097
2098 return range;
2099 }
2100
2101 function ensureCellNodesInRowsCache(row) {
2102 var cacheEntry = rowsCache[row];
2103 if (cacheEntry) {
2104 if (cacheEntry.cellRenderQueue.length) {
2105 var lastChild = cacheEntry.rowNode.lastChild;
2106 while (cacheEntry.cellRenderQueue.length) {
2107 var columnIdx = cacheEntry.cellRenderQueue.pop();
2108 cacheEntry.cellNodesByColumnIdx[columnIdx] = lastChild;
2109 lastChild = lastChild.previousSibling;
2110 }
2111 }
2112 }
2113 }
2114
2115 function cleanUpCells(range, row) {
2116 var totalCellsRemoved = 0;
2117 var cacheEntry = rowsCache[row];
2118
2119 // Remove cells outside the range.
2120 var cellsToRemove = [];
2121 for (var i in cacheEntry.cellNodesByColumnIdx) {
2122 // I really hate it when people mess with Array.prototype.
2123 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(i)) {
2124 continue;
2125 }
2126
2127 // This is a string, so it needs to be cast back to a number.
2128 i = i | 0;
2129
2130 var colspan = cacheEntry.cellColSpans[i];
2131 if (columnPosLeft[i] > range.rightPx ||
2132 columnPosRight[Math.min(columns.length - 1, i + colspan - 1)] < range.leftPx) {
2133 if (!(row == activeRow && i == activeCell)) {
2134 cellsToRemove.push(i);
2135 }
2136 }
2137 }
2138
2139 var cellToRemove, node;
2140 postProcessgroupId++;
2141 while ((cellToRemove = cellsToRemove.pop()) != null) {
2142 node = cacheEntry.cellNodesByColumnIdx[cellToRemove];
2143 if (options.enableAsyncPostRenderCleanup && postProcessedRows[row] && postProcessedRows[row][cellToRemove]) {
2144 queuePostProcessedCellForCleanup(node, cellToRemove, row);
2145 } else {
2146 cacheEntry.rowNode.removeChild(node);
2147 }
2148
2149 delete cacheEntry.cellColSpans[cellToRemove];
2150 delete cacheEntry.cellNodesByColumnIdx[cellToRemove];
2151 if (postProcessedRows[row]) {
2152 delete postProcessedRows[row][cellToRemove];
2153 }
2154 totalCellsRemoved++;
2155 }
2156 }
2157
2158 function cleanUpAndRenderCells(range) {
2159 var cacheEntry;
2160 var stringArray = [];
2161 var processedRows = [];
2162 var cellsAdded;
2163 var totalCellsAdded = 0;
2164 var colspan;
2165
2166 for (var row = range.top, btm = range.bottom; row <= btm; row++) {
2167 cacheEntry = rowsCache[row];
2168 if (!cacheEntry) {
2169 continue;
2170 }
2171
2172 // cellRenderQueue populated in renderRows() needs to be cleared first
2173 ensureCellNodesInRowsCache(row);
2174
2175 cleanUpCells(range, row);
2176
2177 // Render missing cells.
2178 cellsAdded = 0;
2179
2180 var metadata = data.getItemMetadata && data.getItemMetadata(row);
2181 metadata = metadata && metadata.columns;
2182
2183 var d = getDataItem(row);
2184
2185 // TODO: shorten this loop (index? heuristics? binary search?)
2186 for (var i = 0, ii = columns.length; i < ii; i++) {
2187 // Cells to the right are outside the range.
2188 if (columnPosLeft[i] > range.rightPx) {
2189 break;
2190 }
2191
2192 // Already rendered.
2193 if ((colspan = cacheEntry.cellColSpans[i]) != null) {
2194 i += (colspan > 1 ? colspan - 1 : 0);
2195 continue;
2196 }
2197
2198 colspan = 1;
2199 if (metadata) {
2200 var columnData = metadata[columns[i].id] || metadata[i];
2201 colspan = (columnData && columnData.colspan) || 1;
2202 if (colspan === "*") {
2203 colspan = ii - i;
2204 }
2205 }
2206
2207 if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {
2208 appendCellHtml(stringArray, row, i, colspan, d);
2209 cellsAdded++;
2210 }
2211
2212 i += (colspan > 1 ? colspan - 1 : 0);
2213 }
2214
2215 if (cellsAdded) {
2216 totalCellsAdded += cellsAdded;
2217 processedRows.push(row);
2218 }
2219 }
2220
2221 if (!stringArray.length) {
2222 return;
2223 }
2224
2225 var x = document.createElement("div");
2226 x.innerHTML = stringArray.join("");
2227
2228 var processedRow;
2229 var node;
2230 while ((processedRow = processedRows.pop()) != null) {
2231 cacheEntry = rowsCache[processedRow];
2232 var columnIdx;
2233 while ((columnIdx = cacheEntry.cellRenderQueue.pop()) != null) {
2234 node = x.lastChild;
2235 cacheEntry.rowNode.appendChild(node);
2236 cacheEntry.cellNodesByColumnIdx[columnIdx] = node;
2237 }
2238 }
2239 }
2240
2241 function renderRows(range) {
2242 var parentNode = $canvas[0],
2243 stringArray = [],
2244 rows = [],
2245 needToReselectCell = false,
2246 dataLength = getDataLength();
2247
2248 for (var i = range.top, ii = range.bottom; i <= ii; i++) {
2249 if (rowsCache[i]) {
2250 continue;
2251 }
2252 renderedRows++;
2253 rows.push(i);
2254
2255 // Create an entry right away so that appendRowHtml() can
2256 // start populatating it.
2257 rowsCache[i] = {
2258 "rowNode": null,
2259
2260 // ColSpans of rendered cells (by column idx).
2261 // Can also be used for checking whether a cell has been rendered.
2262 "cellColSpans": [],
2263
2264 // Cell nodes (by column idx). Lazy-populated by ensureCellNodesInRowsCache().
2265 "cellNodesByColumnIdx": [],
2266
2267 // Column indices of cell nodes that have been rendered, but not yet indexed in
2268 // cellNodesByColumnIdx. These are in the same order as cell nodes added at the
2269 // end of the row.
2270 "cellRenderQueue": []
2271 };
2272
2273 appendRowHtml(stringArray, i, range, dataLength);
2274 if (activeCellNode && activeRow === i) {
2275 needToReselectCell = true;
2276 }
2277 counter_rows_rendered++;
2278 }
2279
2280 if (!rows.length) { return; }
2281
2282 var x = document.createElement("div");
2283 x.innerHTML = stringArray.join("");
2284
2285 for (var i = 0, ii = rows.length; i < ii; i++) {
2286 rowsCache[rows[i]].rowNode = parentNode.appendChild(x.firstChild);
2287 }
2288
2289 if (needToReselectCell) {
2290 activeCellNode = getCellNode(activeRow, activeCell);
2291 }
2292 }
2293
2294 function startPostProcessing() {
2295 if (!options.enableAsyncPostRender) {
2296 return;
2297 }
2298 clearTimeout(h_postrender);
2299 h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
2300 }
2301
2302 function startPostProcessingCleanup() {
2303 if (!options.enableAsyncPostRenderCleanup) {
2304 return;
2305 }
2306 clearTimeout(h_postrenderCleanup);
2307 h_postrenderCleanup = setTimeout(asyncPostProcessCleanupRows, options.asyncPostRenderCleanupDelay);
2308 }
2309
2310 function invalidatePostProcessingResults(row) {
2311 // change status of columns to be re-rendered
2312 for (var columnIdx in postProcessedRows[row]) {
2313 if (postProcessedRows[row].hasOwnProperty(columnIdx)) {
2314 postProcessedRows[row][columnIdx] = 'C';
2315 }
2316 }
2317 postProcessFromRow = Math.min(postProcessFromRow, row);
2318 postProcessToRow = Math.max(postProcessToRow, row);
2319 startPostProcessing();
2320 }
2321
2322 function updateRowPositions() {
2323 for (var row in rowsCache) {
2324 rowsCache[row].rowNode.style.top = getRowTop(row) + "px";
2325 }
2326 }
2327
2328 function render() {
2329 if (!initialized) { return; }
2330
2331 scrollThrottle.dequeue();
2332
2333 var visible = getVisibleRange();
2334 var rendered = getRenderedRange();
2335
2336 // remove rows no longer in the viewport
2337 cleanupRows(rendered);
2338
2339 // add new rows & missing cells in existing rows
2340 if (lastRenderedScrollLeft != scrollLeft) {
2341 cleanUpAndRenderCells(rendered);
2342 }
2343
2344 // render missing rows
2345 renderRows(rendered);
2346
2347 postProcessFromRow = visible.top;
2348 postProcessToRow = Math.min(getDataLengthIncludingAddNew() - 1, visible.bottom);
2349 startPostProcessing();
2350
2351 lastRenderedScrollTop = scrollTop;
2352 lastRenderedScrollLeft = scrollLeft;
2353 h_render = null;
2354 }
2355
2356 function handleHeaderScroll() {
2357 handleElementScroll($headerScroller[0]);
2358 }
2359
2360 function handleHeaderRowScroll() {
2361 handleElementScroll($headerRowScroller[0]);
2362 }
2363
2364 function handleFooterRowScroll() {
2365 handleElementScroll($footerRowScroller[0]);
2366 }
2367
2368 function handlePreHeaderPanelScroll() {
2369 handleElementScroll($preHeaderPanelScroller[0]);
2370 }
2371
2372 function handleElementScroll(element) {
2373 var scrollLeft = element.scrollLeft;
2374 if (scrollLeft != $viewport[0].scrollLeft) {
2375 $viewport[0].scrollLeft = scrollLeft;
2376 }
2377 }
2378
2379 function handleScroll() {
2380 scrollTop = $viewport[0].scrollTop;
2381 scrollLeft = $viewport[0].scrollLeft;
2382 var vScrollDist = Math.abs(scrollTop - prevScrollTop);
2383 var hScrollDist = Math.abs(scrollLeft - prevScrollLeft);
2384
2385 if (hScrollDist) {
2386 prevScrollLeft = scrollLeft;
2387 $headerScroller[0].scrollLeft = scrollLeft;
2388 $topPanelScroller[0].scrollLeft = scrollLeft;
2389 $headerRowScroller[0].scrollLeft = scrollLeft;
2390 if (options.createFooterRow) {
2391 $footerRowScroller[0].scrollLeft = scrollLeft;
2392 }
2393 if (options.createPreHeaderPanel) {
2394 $preHeaderPanelScroller[0].scrollLeft = scrollLeft;
2395 }
2396 }
2397
2398 if (vScrollDist) {
2399 vScrollDir = prevScrollTop < scrollTop ? 1 : -1;
2400 prevScrollTop = scrollTop;
2401
2402 // switch virtual pages if needed
2403 if (vScrollDist < viewportH) {
2404 scrollTo(scrollTop + offset);
2405 } else {
2406 var oldOffset = offset;
2407 if (h == viewportH) {
2408 page = 0;
2409 } else {
2410 page = Math.min(n - 1, Math.floor(scrollTop * ((th - viewportH) / (h - viewportH)) * (1 / ph)));
2411 }
2412 offset = Math.round(page * cj);
2413 if (oldOffset != offset) {
2414 invalidateAllRows();
2415 }
2416 }
2417 }
2418
2419 if (hScrollDist || vScrollDist) {
2420 var dx = Math.abs(lastRenderedScrollLeft - scrollLeft);
2421 var dy = Math.abs(lastRenderedScrollTop - scrollTop);
2422 if (dx > 20 || dy > 20) {
2423 // if rendering is forced or scrolling is small enough to be "easy", just render
2424 if (options.forceSyncScrolling || (dy < viewportH && dx < viewportW)) {
2425 render();
2426 } else {
2427 // otherwise, perform "difficult" renders at a capped frequency
2428 scrollThrottle.enqueue();
2429 }
2430
2431 trigger(self.onViewportChanged, {});
2432 }
2433 }
2434
2435 trigger(self.onScroll, {scrollLeft: scrollLeft, scrollTop: scrollTop});
2436 }
2437
2438 /*
2439 limits the frequency at which the provided action is executed.
2440 call enqueue to execute the action - it will execute either immediately or, if it was executed less than minPeriod_ms in the past, as soon as minPeriod_ms has expired.
2441 call dequeue to cancel any pending action.
2442 */
2443 function ActionThrottle(action, minPeriod_ms) {
2444
2445 var blocked = false;
2446 var queued = false;
2447
2448 function enqueue() {
2449 if (!blocked) {
2450 blockAndExecute();
2451 } else {
2452 queued = true;
2453 }
2454 }
2455
2456 function dequeue() {
2457 queued = false;
2458 }
2459
2460 function blockAndExecute() {
2461 blocked = true;
2462 setTimeout(unblock, minPeriod_ms);
2463 action();
2464 }
2465
2466 function unblock() {
2467 if (queued) {
2468 dequeue();
2469 blockAndExecute();
2470 } else {
2471 blocked = false;
2472 }
2473 }
2474
2475 return {
2476 enqueue: enqueue,
2477 dequeue: dequeue
2478 }
2479 }
2480
2481 function asyncPostProcessRows() {
2482 var dataLength = getDataLength();
2483 while (postProcessFromRow <= postProcessToRow) {
2484 var row = (vScrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--;
2485 var cacheEntry = rowsCache[row];
2486 if (!cacheEntry || row >= dataLength) {
2487 continue;
2488 }
2489
2490 if (!postProcessedRows[row]) {
2491 postProcessedRows[row] = {};
2492 }
2493
2494 ensureCellNodesInRowsCache(row);
2495 for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
2496 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
2497 continue;
2498 }
2499
2500 columnIdx = columnIdx | 0;
2501
2502 var m = columns[columnIdx];
2503 var processedStatus = postProcessedRows[row][columnIdx]; // C=cleanup and re-render, R=rendered
2504 if (m.asyncPostRender && processedStatus !== 'R') {
2505 var node = cacheEntry.cellNodesByColumnIdx[columnIdx];
2506 if (node) {
2507 m.asyncPostRender(node, row, getDataItem(row), m, (processedStatus === 'C'));
2508 }
2509 postProcessedRows[row][columnIdx] = 'R';
2510 }
2511 }
2512
2513 h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
2514 return;
2515 }
2516 }
2517
2518 function asyncPostProcessCleanupRows() {
2519 if (postProcessedCleanupQueue.length > 0) {
2520 var groupId = postProcessedCleanupQueue[0].groupId;
2521
2522 // loop through all queue members with this groupID
2523 while (postProcessedCleanupQueue.length > 0 && postProcessedCleanupQueue[0].groupId == groupId) {
2524 var entry = postProcessedCleanupQueue.shift();
2525 if (entry.actionType == 'R') {
2526 $(entry.node).remove();
2527 }
2528 if (entry.actionType == 'C') {
2529 var column = columns[entry.columnIdx];
2530 if (column.asyncPostRenderCleanup && entry.node) {
2531 // cleanup must also remove element
2532 column.asyncPostRenderCleanup(entry.node, entry.rowIdx, column);
2533 }
2534 }
2535 }
2536
2537 // call this function again after the specified delay
2538 h_postrenderCleanup = setTimeout(asyncPostProcessCleanupRows, options.asyncPostRenderCleanupDelay);
2539 }
2540 }
2541
2542 function updateCellCssStylesOnRenderedRows(addedHash, removedHash) {
2543 var node, columnId, addedRowHash, removedRowHash;
2544 for (var row in rowsCache) {
2545 removedRowHash = removedHash && removedHash[row];
2546 addedRowHash = addedHash && addedHash[row];
2547
2548 if (removedRowHash) {
2549 for (columnId in removedRowHash) {
2550 if (!addedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
2551 node = getCellNode(row, getColumnIndex(columnId));
2552 if (node) {
2553 $(node).removeClass(removedRowHash[columnId]);
2554 }
2555 }
2556 }
2557 }
2558
2559 if (addedRowHash) {
2560 for (columnId in addedRowHash) {
2561 if (!removedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
2562 node = getCellNode(row, getColumnIndex(columnId));
2563 if (node) {
2564 $(node).addClass(addedRowHash[columnId]);
2565 }
2566 }
2567 }
2568 }
2569 }
2570 }
2571
2572 function addCellCssStyles(key, hash) {
2573 if (cellCssClasses[key]) {
2574 throw new Error("addCellCssStyles: cell CSS hash with key '" + key + "' already exists.");
2575 }
2576
2577 cellCssClasses[key] = hash;
2578 updateCellCssStylesOnRenderedRows(hash, null);
2579
2580 trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash, "grid": self });
2581 }
2582
2583 function removeCellCssStyles(key) {
2584 if (!cellCssClasses[key]) {
2585 return;
2586 }
2587
2588 updateCellCssStylesOnRenderedRows(null, cellCssClasses[key]);
2589 delete cellCssClasses[key];
2590
2591 trigger(self.onCellCssStylesChanged, { "key": key, "hash": null, "grid": self });
2592 }
2593
2594 function setCellCssStyles(key, hash) {
2595 var prevHash = cellCssClasses[key];
2596
2597 cellCssClasses[key] = hash;
2598 updateCellCssStylesOnRenderedRows(hash, prevHash);
2599
2600 trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash, "grid": self });
2601 }
2602
2603 function getCellCssStyles(key) {
2604 return cellCssClasses[key];
2605 }
2606
2607 function flashCell(row, cell, speed) {
2608 speed = speed || 100;
2609 if (rowsCache[row]) {
2610 var $cell = $(getCellNode(row, cell));
2611
2612 function toggleCellClass(times) {
2613 if (!times) {
2614 return;
2615 }
2616 setTimeout(function () {
2617 $cell.queue(function () {
2618 $cell.toggleClass(options.cellFlashingCssClass).dequeue();
2619 toggleCellClass(times - 1);
2620 });
2621 },
2622 speed);
2623 }
2624
2625 toggleCellClass(4);
2626 }
2627 }
2628
2629 //////////////////////////////////////////////////////////////////////////////////////////////
2630 // Interactivity
2631
2632 function handleMouseWheel(e) {
2633 var rowNode = $(e.target).closest(".slick-row")[0];
2634 if (rowNode != rowNodeFromLastMouseWheelEvent) {
2635 if (zombieRowNodeFromLastMouseWheelEvent && zombieRowNodeFromLastMouseWheelEvent != rowNode) {
2636 if (options.enableAsyncPostRenderCleanup && zombieRowPostProcessedFromLastMouseWheelEvent) {
2637 queuePostProcessedRowForCleanup(zombieRowCacheFromLastMouseWheelEvent,
2638 zombieRowPostProcessedFromLastMouseWheelEvent);
2639 } else {
2640 $canvas[0].removeChild(zombieRowNodeFromLastMouseWheelEvent);
2641 }
2642 zombieRowNodeFromLastMouseWheelEvent = null;
2643 zombieRowCacheFromLastMouseWheelEvent = null;
2644 zombieRowPostProcessedFromLastMouseWheelEvent = null;
2645
2646 if (options.enableAsyncPostRenderCleanup) { startPostProcessingCleanup(); }
2647 }
2648 rowNodeFromLastMouseWheelEvent = rowNode;
2649 }
2650 }
2651
2652 function handleDragInit(e, dd) {
2653 var cell = getCellFromEvent(e);
2654 if (!cell || !cellExists(cell.row, cell.cell)) {
2655 return false;
2656 }
2657
2658 var retval = trigger(self.onDragInit, dd, e);
2659 if (e.isImmediatePropagationStopped()) {
2660 return retval;
2661 }
2662
2663 // if nobody claims to be handling drag'n'drop by stopping immediate propagation,
2664 // cancel out of it
2665 return false;
2666 }
2667
2668 function handleDragStart(e, dd) {
2669 var cell = getCellFromEvent(e);
2670 if (!cell || !cellExists(cell.row, cell.cell)) {
2671 return false;
2672 }
2673
2674 var retval = trigger(self.onDragStart, dd, e);
2675 if (e.isImmediatePropagationStopped()) {
2676 return retval;
2677 }
2678
2679 return false;
2680 }
2681
2682 function handleDrag(e, dd) {
2683 return trigger(self.onDrag, dd, e);
2684 }
2685
2686 function handleDragEnd(e, dd) {
2687 trigger(self.onDragEnd, dd, e);
2688 }
2689
2690 function handleKeyDown(e) {
2691 trigger(self.onKeyDown, {row: activeRow, cell: activeCell}, e);
2692 var handled = e.isImmediatePropagationStopped();
2693 var keyCode = Slick.keyCode;
2694
2695 if (!handled) {
2696 if (!e.shiftKey && !e.altKey) {
2697 if (options.editable && currentEditor && currentEditor.keyCaptureList) {
2698 if (currentEditor.keyCaptureList.indexOf(e.which) > -1) {
2699 return;
2700 }
2701 }
2702 if (e.which == keyCode.HOME) {
2703 handled = (e.ctrlKey) ? navigateTop() : navigateRowStart();
2704 } else if (e.which == keyCode.END) {
2705 handled = (e.ctrlKey) ? navigateBottom() : navigateRowEnd();
2706 }
2707 }
2708 }
2709 if (!handled) {
2710 if (!e.shiftKey && !e.altKey && !e.ctrlKey) {
2711 // editor may specify an array of keys to bubble
2712 if (options.editable && currentEditor && currentEditor.keyCaptureList) {
2713 if (currentEditor.keyCaptureList.indexOf( e.which ) > -1) {
2714 return;
2715 }
2716 }
2717 if (e.which == keyCode.ESCAPE) {
2718 if (!getEditorLock().isActive()) {
2719 return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event)
2720 }
2721 cancelEditAndSetFocus();
2722 } else if (e.which == keyCode.PAGE_DOWN) {
2723 navigatePageDown();
2724 handled = true;
2725 } else if (e.which == keyCode.PAGE_UP) {
2726 navigatePageUp();
2727 handled = true;
2728 } else if (e.which == keyCode.LEFT) {
2729 handled = navigateLeft();
2730 } else if (e.which == keyCode.RIGHT) {
2731 handled = navigateRight();
2732 } else if (e.which == keyCode.UP) {
2733 handled = navigateUp();
2734 } else if (e.which == keyCode.DOWN) {
2735 handled = navigateDown();
2736 } else if (e.which == keyCode.TAB) {
2737 handled = navigateNext();
2738 } else if (e.which == keyCode.ENTER) {
2739 if (options.editable) {
2740 if (currentEditor) {
2741 // adding new row
2742 if (activeRow === getDataLength()) {
2743 navigateDown();
2744 } else {
2745 commitEditAndSetFocus();
2746 }
2747 } else {
2748 if (getEditorLock().commitCurrentEdit()) {
2749 makeActiveCellEditable();
2750 }
2751 }
2752 }
2753 handled = true;
2754 }
2755 } else if (e.which == keyCode.TAB && e.shiftKey && !e.ctrlKey && !e.altKey) {
2756 handled = navigatePrev();
2757 }
2758 }
2759
2760 if (handled) {
2761 // the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it
2762 e.stopPropagation();
2763 e.preventDefault();
2764 try {
2765 e.originalEvent.keyCode = 0; // prevent default behaviour for special keys in IE browsers (F3, F5, etc.)
2766 }
2767 // ignore exceptions - setting the original event's keycode throws access denied exception for "Ctrl"
2768 // (hitting control key only, nothing else), "Shift" (maybe others)
2769 catch (error) {
2770 }
2771 }
2772 }
2773
2774 function handleClick(e) {
2775 if (!currentEditor) {
2776 // if this click resulted in some cell child node getting focus,
2777 // don't steal it back - keyboard events will still bubble up
2778 // IE9+ seems to default DIVs to tabIndex=0 instead of -1, so check for cell clicks directly.
2779 if (e.target != document.activeElement || $(e.target).hasClass("slick-cell")) {
2780 setFocus();
2781 }
2782 }
2783
2784 var cell = getCellFromEvent(e);
2785 if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
2786 return;
2787 }
2788
2789 trigger(self.onClick, {row: cell.row, cell: cell.cell}, e);
2790 if (e.isImmediatePropagationStopped()) {
2791 return;
2792 }
2793
2794 // this optimisation causes trouble - MLeibman #329
2795 //if ((activeCell != cell.cell || activeRow != cell.row) && canCellBeActive(cell.row, cell.cell)) {
2796 if (canCellBeActive(cell.row, cell.cell)) {
2797 if (!getEditorLock().isActive() || getEditorLock().commitCurrentEdit()) {
2798 scrollRowIntoView(cell.row, false);
2799
2800 var preClickModeOn = (e.target && e.target.className === Slick.preClickClassName);
2801 var column = columns[cell.cell];
2802 var suppressActiveCellChangedEvent = (options.editable && column && column.editor && options.suppressActiveCellChangeOnEdit) ? true : false;
2803 setActiveCellInternal(getCellNode(cell.row, cell.cell), null, preClickModeOn, suppressActiveCellChangedEvent);
2804 }
2805 }
2806 }
2807
2808 function handleContextMenu(e) {
2809 var $cell = $(e.target).closest(".slick-cell", $canvas);
2810 if ($cell.length === 0) {
2811 return;
2812 }
2813
2814 // are we editing this cell?
2815 if (activeCellNode === $cell[0] && currentEditor !== null) {
2816 return;
2817 }
2818
2819 trigger(self.onContextMenu, {}, e);
2820 }
2821
2822 function handleDblClick(e) {
2823 var cell = getCellFromEvent(e);
2824 if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
2825 return;
2826 }
2827
2828 trigger(self.onDblClick, {row: cell.row, cell: cell.cell}, e);
2829 if (e.isImmediatePropagationStopped()) {
2830 return;
2831 }
2832
2833 if (options.editable) {
2834 gotoCell(cell.row, cell.cell, true);
2835 }
2836 }
2837
2838 function handleHeaderMouseEnter(e) {
2839 trigger(self.onHeaderMouseEnter, {
2840 "column": $(this).data("column"),
2841 "grid": self
2842 }, e);
2843 }
2844
2845 function handleHeaderMouseLeave(e) {
2846 trigger(self.onHeaderMouseLeave, {
2847 "column": $(this).data("column"),
2848 "grid": self
2849 }, e);
2850 }
2851
2852 function handleHeaderContextMenu(e) {
2853 var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
2854 var column = $header && $header.data("column");
2855 trigger(self.onHeaderContextMenu, {column: column}, e);
2856 }
2857
2858 function handleHeaderClick(e) {
2859 if (columnResizeDragging) return;
2860 var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
2861 var column = $header && $header.data("column");
2862 if (column) {
2863 trigger(self.onHeaderClick, {column: column}, e);
2864 }
2865 }
2866
2867 function handleMouseEnter(e) {
2868 trigger(self.onMouseEnter, {}, e);
2869 }
2870
2871 function handleMouseLeave(e) {
2872 trigger(self.onMouseLeave, {}, e);
2873 }
2874
2875 function cellExists(row, cell) {
2876 return !(row < 0 || row >= getDataLength() || cell < 0 || cell >= columns.length);
2877 }
2878
2879 function getCellFromPoint(x, y) {
2880 var row = getRowFromPosition(y);
2881 var cell = 0;
2882
2883 var w = 0;
2884 for (var i = 0; i < columns.length && w < x; i++) {
2885 w += columns[i].width;
2886 cell++;
2887 }
2888
2889 if (cell < 0) {
2890 cell = 0;
2891 }
2892
2893 return {row: row, cell: cell - 1};
2894 }
2895
2896 function getCellFromNode(cellNode) {
2897 // read column number from .l<columnNumber> CSS class
2898 var cls = /l\d+/.exec(cellNode.className);
2899 if (!cls) {
2900 throw new Error("getCellFromNode: cannot get cell - " + cellNode.className);
2901 }
2902 return parseInt(cls[0].substr(1, cls[0].length - 1), 10);
2903 }
2904
2905 function getRowFromNode(rowNode) {
2906 for (var row in rowsCache) {
2907 if (rowsCache[row].rowNode === rowNode) {
2908 return row | 0;
2909 }
2910 }
2911
2912 return null;
2913 }
2914
2915 function getCellFromEvent(e) {
2916 var $cell = $(e.target).closest(".slick-cell", $canvas);
2917 if (!$cell.length) {
2918 return null;
2919 }
2920
2921 var row = getRowFromNode($cell[0].parentNode);
2922 var cell = getCellFromNode($cell[0]);
2923
2924 if (row == null || cell == null) {
2925 return null;
2926 } else {
2927 return {
2928 "row": row,
2929 "cell": cell
2930 };
2931 }
2932 }
2933
2934 function getCellNodeBox(row, cell) {
2935 if (!cellExists(row, cell)) {
2936 return null;
2937 }
2938
2939 var y1 = getRowTop(row);
2940 var y2 = y1 + options.rowHeight - 1;
2941 var x1 = 0;
2942 for (var i = 0; i < cell; i++) {
2943 x1 += columns[i].width;
2944 }
2945 var x2 = x1 + columns[cell].width;
2946
2947 return {
2948 top: y1,
2949 left: x1,
2950 bottom: y2,
2951 right: x2
2952 };
2953 }
2954
2955 //////////////////////////////////////////////////////////////////////////////////////////////
2956 // Cell switching
2957
2958 function resetActiveCell() {
2959 setActiveCellInternal(null, false);
2960 }
2961
2962 function setFocus() {
2963 if (tabbingDirection == -1) {
2964 $focusSink[0].focus();
2965 } else {
2966 $focusSink2[0].focus();
2967 }
2968 }
2969
2970 function scrollCellIntoView(row, cell, doPaging) {
2971 scrollRowIntoView(row, doPaging);
2972
2973 var colspan = getColspan(row, cell);
2974 internalScrollColumnIntoView(columnPosLeft[cell], columnPosRight[cell + (colspan > 1 ? colspan - 1 : 0)]);
2975 }
2976
2977 function internalScrollColumnIntoView(left, right) {
2978 var scrollRight = scrollLeft + viewportW;
2979
2980 if (left < scrollLeft) {
2981 $viewport.scrollLeft(left);
2982 handleScroll();
2983 render();
2984 } else if (right > scrollRight) {
2985 $viewport.scrollLeft(Math.min(left, right - $viewport[0].clientWidth));
2986 handleScroll();
2987 render();
2988 }
2989 }
2990
2991 function scrollColumnIntoView(cell) {
2992 internalScrollColumnIntoView(columnPosLeft[cell], columnPosRight[cell]);
2993 }
2994
2995 function setActiveCellInternal(newCell, opt_editMode, preClickModeOn, suppressActiveCellChangedEvent) {
2996 if (activeCellNode !== null) {
2997 makeActiveCellNormal();
2998 $(activeCellNode).removeClass("active");
2999 if (rowsCache[activeRow]) {
3000 $(rowsCache[activeRow].rowNode).removeClass("active");
3001 }
3002 }
3003
3004 var activeCellChanged = (activeCellNode !== newCell);
3005 activeCellNode = newCell;
3006
3007 if (activeCellNode != null) {
3008 activeRow = getRowFromNode(activeCellNode.parentNode);
3009 activeCell = activePosX = getCellFromNode(activeCellNode);
3010
3011 if (opt_editMode == null) {
3012 opt_editMode = (activeRow == getDataLength()) || options.autoEdit;
3013 }
3014
3015 if (options.showCellSelection) {
3016 $(activeCellNode).addClass("active");
3017 $(rowsCache[activeRow].rowNode).addClass("active");
3018 }
3019
3020 if (options.editable && opt_editMode && isCellPotentiallyEditable(activeRow, activeCell)) {
3021 clearTimeout(h_editorLoader);
3022
3023 if (options.asyncEditorLoading) {
3024 h_editorLoader = setTimeout(function () {
3025 makeActiveCellEditable(undefined, preClickModeOn);
3026 }, options.asyncEditorLoadDelay);
3027 } else {
3028 makeActiveCellEditable(undefined, preClickModeOn);
3029 }
3030 }
3031 } else {
3032 activeRow = activeCell = null;
3033 }
3034
3035 // this optimisation causes trouble - MLeibman #329
3036 //if (activeCellChanged) {
3037 if (!suppressActiveCellChangedEvent) { trigger(self.onActiveCellChanged, getActiveCell()); }
3038 //}
3039 }
3040
3041 function clearTextSelection() {
3042 if (document.selection && document.selection.empty) {
3043 try {
3044 //IE fails here if selected element is not in dom
3045 document.selection.empty();
3046 } catch (e) { }
3047 } else if (window.getSelection) {
3048 var sel = window.getSelection();
3049 if (sel && sel.removeAllRanges) {
3050 sel.removeAllRanges();
3051 }
3052 }
3053 }
3054
3055 function isCellPotentiallyEditable(row, cell) {
3056 var dataLength = getDataLength();
3057 // is the data for this row loaded?
3058 if (row < dataLength && !getDataItem(row)) {
3059 return false;
3060 }
3061
3062 // are we in the Add New row? can we create new from this cell?
3063 if (columns[cell].cannotTriggerInsert && row >= dataLength) {
3064 return false;
3065 }
3066
3067 // does this cell have an editor?
3068 if (!getEditor(row, cell)) {
3069 return false;
3070 }
3071
3072 return true;
3073 }
3074
3075 function makeActiveCellNormal() {
3076 if (!currentEditor) {
3077 return;
3078 }
3079 trigger(self.onBeforeCellEditorDestroy, {editor: currentEditor});
3080 currentEditor.destroy();
3081 currentEditor = null;
3082
3083 if (activeCellNode) {
3084 var d = getDataItem(activeRow);
3085 $(activeCellNode).removeClass("editable invalid");
3086 if (d) {
3087 var column = columns[activeCell];
3088 var formatter = getFormatter(activeRow, column);
3089 var formatterResult = formatter(activeRow, activeCell, getDataItemValueForColumn(d, column), column, d, self);
3090 applyFormatResultToCellNode(formatterResult, activeCellNode);
3091 invalidatePostProcessingResults(activeRow);
3092 }
3093 }
3094
3095 // if there previously was text selected on a page (such as selected text in the edit cell just removed),
3096 // IE can't set focus to anything else correctly
3097 if (navigator.userAgent.toLowerCase().match(/msie/)) {
3098 clearTextSelection();
3099 }
3100
3101 getEditorLock().deactivate(editController);
3102 }
3103
3104 function makeActiveCellEditable(editor, preClickModeOn) {
3105 if (!activeCellNode) {
3106 return;
3107 }
3108 if (!options.editable) {
3109 throw new Error("Grid : makeActiveCellEditable : should never get called when options.editable is false");
3110 }
3111
3112 // cancel pending async call if there is one
3113 clearTimeout(h_editorLoader);
3114
3115 if (!isCellPotentiallyEditable(activeRow, activeCell)) {
3116 return;
3117 }
3118
3119 var columnDef = columns[activeCell];
3120 var item = getDataItem(activeRow);
3121
3122 if (trigger(self.onBeforeEditCell, {row: activeRow, cell: activeCell, item: item, column: columnDef}) === false) {
3123 setFocus();
3124 return;
3125 }
3126
3127 getEditorLock().activate(editController);
3128 $(activeCellNode).addClass("editable");
3129
3130 var useEditor = editor || getEditor(activeRow, activeCell);
3131
3132 // don't clear the cell if a custom editor is passed through
3133 if (!editor && !useEditor.suppressClearOnEdit) {
3134 activeCellNode.innerHTML = "";
3135 }
3136
3137 currentEditor = new useEditor({
3138 grid: self,
3139 gridPosition: absBox($container[0]),
3140 position: absBox(activeCellNode),
3141 container: activeCellNode,
3142 column: columnDef,
3143 item: item || {},
3144 commitChanges: commitEditAndSetFocus,
3145 cancelChanges: cancelEditAndSetFocus
3146 });
3147
3148 if (item) {
3149 currentEditor.loadValue(item);
3150 if (preClickModeOn && currentEditor.preClick) {
3151 currentEditor.preClick();
3152 }
3153 }
3154
3155 serializedEditorValue = currentEditor.serializeValue();
3156
3157 if (currentEditor.position) {
3158 handleActiveCellPositionChange();
3159 }
3160 }
3161
3162 function commitEditAndSetFocus() {
3163 // if the commit fails, it would do so due to a validation error
3164 // if so, do not steal the focus from the editor
3165 if (getEditorLock().commitCurrentEdit()) {
3166 setFocus();
3167 if (options.autoEdit) {
3168 navigateDown();
3169 }
3170 }
3171 }
3172
3173 function cancelEditAndSetFocus() {
3174 if (getEditorLock().cancelCurrentEdit()) {
3175 setFocus();
3176 }
3177 }
3178
3179 function absBox(elem) {
3180 var box = {
3181 top: elem.offsetTop,
3182 left: elem.offsetLeft,
3183 bottom: 0,
3184 right: 0,
3185 width: $(elem).outerWidth(),
3186 height: $(elem).outerHeight(),
3187 visible: true};
3188 box.bottom = box.top + box.height;
3189 box.right = box.left + box.width;
3190
3191 // walk up the tree
3192 var offsetParent = elem.offsetParent;
3193 while ((elem = elem.parentNode) != document.body) {
3194 if (elem == null) break;
3195
3196 if (box.visible && elem.scrollHeight != elem.offsetHeight && $(elem).css("overflowY") != "visible") {
3197 box.visible = box.bottom > elem.scrollTop && box.top < elem.scrollTop + elem.clientHeight;
3198 }
3199
3200 if (box.visible && elem.scrollWidth != elem.offsetWidth && $(elem).css("overflowX") != "visible") {
3201 box.visible = box.right > elem.scrollLeft && box.left < elem.scrollLeft + elem.clientWidth;
3202 }
3203
3204 box.left -= elem.scrollLeft;
3205 box.top -= elem.scrollTop;
3206
3207 if (elem === offsetParent) {
3208 box.left += elem.offsetLeft;
3209 box.top += elem.offsetTop;
3210 offsetParent = elem.offsetParent;
3211 }
3212
3213 box.bottom = box.top + box.height;
3214 box.right = box.left + box.width;
3215 }
3216
3217 return box;
3218 }
3219
3220 function getActiveCellPosition() {
3221 return absBox(activeCellNode);
3222 }
3223
3224 function getGridPosition() {
3225 return absBox($container[0])
3226 }
3227
3228 function handleActiveCellPositionChange() {
3229 if (!activeCellNode) {
3230 return;
3231 }
3232
3233 trigger(self.onActiveCellPositionChanged, {});
3234
3235 if (currentEditor) {
3236 var cellBox = getActiveCellPosition();
3237 if (currentEditor.show && currentEditor.hide) {
3238 if (!cellBox.visible) {
3239 currentEditor.hide();
3240 } else {
3241 currentEditor.show();
3242 }
3243 }
3244
3245 if (currentEditor.position) {
3246 currentEditor.position(cellBox);
3247 }
3248 }
3249 }
3250
3251 function getCellEditor() {
3252 return currentEditor;
3253 }
3254
3255 function getActiveCell() {
3256 if (!activeCellNode) {
3257 return null;
3258 } else {
3259 return {row: activeRow, cell: activeCell};
3260 }
3261 }
3262
3263 function getActiveCellNode() {
3264 return activeCellNode;
3265 }
3266
3267 function scrollRowIntoView(row, doPaging) {
3268 var rowAtTop = row * options.rowHeight;
3269 var rowAtBottom = (row + 1) * options.rowHeight - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0);
3270
3271 // need to page down?
3272 if ((row + 1) * options.rowHeight > scrollTop + viewportH + offset) {
3273 scrollTo(doPaging ? rowAtTop : rowAtBottom);
3274 render();
3275 }
3276 // or page up?
3277 else if (row * options.rowHeight < scrollTop + offset) {
3278 scrollTo(doPaging ? rowAtBottom : rowAtTop);
3279 render();
3280 }
3281 }
3282
3283 function scrollRowToTop(row) {
3284 scrollTo(row * options.rowHeight);
3285 render();
3286 }
3287
3288 function scrollPage(dir) {
3289 var deltaRows = dir * numVisibleRows;
3290 scrollTo((getRowFromPosition(scrollTop) + deltaRows) * options.rowHeight);
3291 render();
3292
3293 if (options.enableCellNavigation && activeRow != null) {
3294 var row = activeRow + deltaRows;
3295 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew();
3296 if (row >= dataLengthIncludingAddNew) {
3297 row = dataLengthIncludingAddNew - 1;
3298 }
3299 if (row < 0) {
3300 row = 0;
3301 }
3302
3303 var cell = 0, prevCell = null;
3304 var prevActivePosX = activePosX;
3305 while (cell <= activePosX) {
3306 if (canCellBeActive(row, cell)) {
3307 prevCell = cell;
3308 }
3309 cell += getColspan(row, cell);
3310 }
3311
3312 if (prevCell !== null) {
3313 setActiveCellInternal(getCellNode(row, prevCell));
3314 activePosX = prevActivePosX;
3315 } else {
3316 resetActiveCell();
3317 }
3318 }
3319 }
3320
3321 function navigatePageDown() {
3322 scrollPage(1);
3323 }
3324
3325 function navigatePageUp() {
3326 scrollPage(-1);
3327 }
3328
3329 function navigateTop() {
3330 navigateToRow(0);
3331 }
3332
3333 function navigateBottom() {
3334 navigateToRow(getDataLength()-1);
3335 }
3336
3337 function navigateToRow(row) {
3338 var num_rows = getDataLength();
3339 if (!num_rows) return true;
3340
3341 if (row < 0) row = 0;
3342 else if (row >= num_rows) row = num_rows - 1;
3343
3344 scrollCellIntoView(row, 0, true);
3345 if (options.enableCellNavigation && activeRow != null) {
3346 var cell = 0, prevCell = null;
3347 var prevActivePosX = activePosX;
3348 while (cell <= activePosX) {
3349 if (canCellBeActive(row, cell)) {
3350 prevCell = cell;
3351 }
3352 cell += getColspan(row, cell);
3353 }
3354
3355 if (prevCell !== null) {
3356 setActiveCellInternal(getCellNode(row, prevCell));
3357 activePosX = prevActivePosX;
3358 } else {
3359 resetActiveCell();
3360 }
3361 }
3362 return true;
3363 }
3364
3365 function getColspan(row, cell) {
3366 var metadata = data.getItemMetadata && data.getItemMetadata(row);
3367 if (!metadata || !metadata.columns) {
3368 return 1;
3369 }
3370
3371 var columnData = metadata.columns[columns[cell].id] || metadata.columns[cell];
3372 var colspan = (columnData && columnData.colspan);
3373 if (colspan === "*") {
3374 colspan = columns.length - cell;
3375 } else {
3376 colspan = colspan || 1;
3377 }
3378
3379 return colspan;
3380 }
3381
3382 function findFirstFocusableCell(row) {
3383 var cell = 0;
3384 while (cell < columns.length) {
3385 if (canCellBeActive(row, cell)) {
3386 return cell;
3387 }
3388 cell += getColspan(row, cell);
3389 }
3390 return null;
3391 }
3392
3393 function findLastFocusableCell(row) {
3394 var cell = 0;
3395 var lastFocusableCell = null;
3396 while (cell < columns.length) {
3397 if (canCellBeActive(row, cell)) {
3398 lastFocusableCell = cell;
3399 }
3400 cell += getColspan(row, cell);
3401 }
3402 return lastFocusableCell;
3403 }
3404
3405 function gotoRight(row, cell, posX) {
3406 if (cell >= columns.length) {
3407 return null;
3408 }
3409
3410 do {
3411 cell += getColspan(row, cell);
3412 }
3413 while (cell < columns.length && !canCellBeActive(row, cell));
3414
3415 if (cell < columns.length) {
3416 return {
3417 "row": row,
3418 "cell": cell,
3419 "posX": cell
3420 };
3421 }
3422 return null;
3423 }
3424
3425 function gotoLeft(row, cell, posX) {
3426 if (cell <= 0) {
3427 return null;
3428 }
3429
3430 var firstFocusableCell = findFirstFocusableCell(row);
3431 if (firstFocusableCell === null || firstFocusableCell >= cell) {
3432 return null;
3433 }
3434
3435 var prev = {
3436 "row": row,
3437 "cell": firstFocusableCell,
3438 "posX": firstFocusableCell
3439 };
3440 var pos;
3441 while (true) {
3442 pos = gotoRight(prev.row, prev.cell, prev.posX);
3443 if (!pos) {
3444 return null;
3445 }
3446 if (pos.cell >= cell) {
3447 return prev;
3448 }
3449 prev = pos;
3450 }
3451 }
3452
3453 function gotoDown(row, cell, posX) {
3454 var prevCell;
3455 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew();
3456 while (true) {
3457 if (++row >= dataLengthIncludingAddNew) {
3458 return null;
3459 }
3460
3461 prevCell = cell = 0;
3462 while (cell <= posX) {
3463 prevCell = cell;
3464 cell += getColspan(row, cell);
3465 }
3466
3467 if (canCellBeActive(row, prevCell)) {
3468 return {
3469 "row": row,
3470 "cell": prevCell,
3471 "posX": posX
3472 };
3473 }
3474 }
3475 }
3476
3477 function gotoUp(row, cell, posX) {
3478 var prevCell;
3479 while (true) {
3480 if (--row < 0) {
3481 return null;
3482 }
3483
3484 prevCell = cell = 0;
3485 while (cell <= posX) {
3486 prevCell = cell;
3487 cell += getColspan(row, cell);
3488 }
3489
3490 if (canCellBeActive(row, prevCell)) {
3491 return {
3492 "row": row,
3493 "cell": prevCell,
3494 "posX": posX
3495 };
3496 }
3497 }
3498 }
3499
3500 function gotoNext(row, cell, posX) {
3501 if (row == null && cell == null) {
3502 row = cell = posX = 0;
3503 if (canCellBeActive(row, cell)) {
3504 return {
3505 "row": row,
3506 "cell": cell,
3507 "posX": cell
3508 };
3509 }
3510 }
3511
3512 var pos = gotoRight(row, cell, posX);
3513 if (pos) {
3514 return pos;
3515 }
3516
3517 var firstFocusableCell = null;
3518 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew();
3519
3520 // if at last row, cycle through columns rather than get stuck in the last one
3521 if (row === dataLengthIncludingAddNew - 1) { row--; }
3522
3523 while (++row < dataLengthIncludingAddNew) {
3524 firstFocusableCell = findFirstFocusableCell(row);
3525 if (firstFocusableCell !== null) {
3526 return {
3527 "row": row,
3528 "cell": firstFocusableCell,
3529 "posX": firstFocusableCell
3530 };
3531 }
3532 }
3533 return null;
3534 }
3535
3536 function gotoPrev(row, cell, posX) {
3537 if (row == null && cell == null) {
3538 row = getDataLengthIncludingAddNew() - 1;
3539 cell = posX = columns.length - 1;
3540 if (canCellBeActive(row, cell)) {
3541 return {
3542 "row": row,
3543 "cell": cell,
3544 "posX": cell
3545 };
3546 }
3547 }
3548
3549 var pos;
3550 var lastSelectableCell;
3551 while (!pos) {
3552 pos = gotoLeft(row, cell, posX);
3553 if (pos) {
3554 break;
3555 }
3556 if (--row < 0) {
3557 return null;
3558 }
3559
3560 cell = 0;
3561 lastSelectableCell = findLastFocusableCell(row);
3562 if (lastSelectableCell !== null) {
3563 pos = {
3564 "row": row,
3565 "cell": lastSelectableCell,
3566 "posX": lastSelectableCell
3567 };
3568 }
3569 }
3570 return pos;
3571 }
3572
3573 function gotoRowStart(row, cell, posX) {
3574 var newCell = findFirstFocusableCell(row);
3575 if (newCell === null) return null;
3576
3577 return {
3578 "row": row,
3579 "cell": newCell,
3580 "posX": newCell
3581 };
3582 }
3583
3584 function gotoRowEnd(row, cell, posX) {
3585 var newCell = findLastFocusableCell(row);
3586 if (newCell === null) return null;
3587
3588 return {
3589 "row": row,
3590 "cell": newCell,
3591 "posX": newCell
3592 };
3593 }
3594
3595 function navigateRight() {
3596 return navigate("right");
3597 }
3598
3599 function navigateLeft() {
3600 return navigate("left");
3601 }
3602
3603 function navigateDown() {
3604 return navigate("down");
3605 }
3606
3607 function navigateUp() {
3608 return navigate("up");
3609 }
3610
3611 function navigateNext() {
3612 return navigate("next");
3613 }
3614
3615 function navigatePrev() {
3616 return navigate("prev");
3617 }
3618
3619 function navigateRowStart() {
3620 return navigate("home");
3621 }
3622
3623 function navigateRowEnd() {
3624 return navigate("end");
3625 }
3626
3627 /**
3628 * @param {string} dir Navigation direction.
3629 * @return {boolean} Whether navigation resulted in a change of active cell.
3630 */
3631 function navigate(dir) {
3632 if (!options.enableCellNavigation) {
3633 return false;
3634 }
3635
3636 if (!activeCellNode && dir != "prev" && dir != "next") {
3637 return false;
3638 }
3639
3640 if (!getEditorLock().commitCurrentEdit()) {
3641 return true;
3642 }
3643 setFocus();
3644
3645 var tabbingDirections = {
3646 "up": -1,
3647 "down": 1,
3648 "left": -1,
3649 "right": 1,
3650 "prev": -1,
3651 "next": 1,
3652 "home": -1,
3653 "end": 1
3654 };
3655 tabbingDirection = tabbingDirections[dir];
3656
3657 var stepFunctions = {
3658 "up": gotoUp,
3659 "down": gotoDown,
3660 "left": gotoLeft,
3661 "right": gotoRight,
3662 "prev": gotoPrev,
3663 "next": gotoNext,
3664 "home": gotoRowStart,
3665 "end": gotoRowEnd
3666 };
3667 var stepFn = stepFunctions[dir];
3668 var pos = stepFn(activeRow, activeCell, activePosX);
3669 if (pos) {
3670 var isAddNewRow = (pos.row == getDataLength());
3671 scrollCellIntoView(pos.row, pos.cell, !isAddNewRow && options.emulatePagingWhenScrolling);
3672 setActiveCellInternal(getCellNode(pos.row, pos.cell));
3673 activePosX = pos.posX;
3674 return true;
3675 } else {
3676 setActiveCellInternal(getCellNode(activeRow, activeCell));
3677 return false;
3678 }
3679 }
3680
3681 function getCellNode(row, cell) {
3682 if (rowsCache[row]) {
3683 ensureCellNodesInRowsCache(row);
3684 return rowsCache[row].cellNodesByColumnIdx[cell];
3685 }
3686 return null;
3687 }
3688
3689 function setActiveCell(row, cell, opt_editMode, preClickModeOn, suppressActiveCellChangedEvent) {
3690 if (!initialized) { return; }
3691 if (row > getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
3692 return;
3693 }
3694
3695 if (!options.enableCellNavigation) {
3696 return;
3697 }
3698
3699 scrollCellIntoView(row, cell, false);
3700 setActiveCellInternal(getCellNode(row, cell), opt_editMode, preClickModeOn, suppressActiveCellChangedEvent);
3701 }
3702
3703 function canCellBeActive(row, cell) {
3704 if (!options.enableCellNavigation || row >= getDataLengthIncludingAddNew() ||
3705 row < 0 || cell >= columns.length || cell < 0) {
3706 return false;
3707 }
3708
3709 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
3710 if (rowMetadata && typeof rowMetadata.focusable !== "undefined") {
3711 return !!rowMetadata.focusable;
3712 }
3713
3714 var columnMetadata = rowMetadata && rowMetadata.columns;
3715 if (columnMetadata && columnMetadata[columns[cell].id] && typeof columnMetadata[columns[cell].id].focusable !== "undefined") {
3716 return !!columnMetadata[columns[cell].id].focusable;
3717 }
3718 if (columnMetadata && columnMetadata[cell] && typeof columnMetadata[cell].focusable !== "undefined") {
3719 return !!columnMetadata[cell].focusable;
3720 }
3721
3722 return !!columns[cell].focusable;
3723 }
3724
3725 function canCellBeSelected(row, cell) {
3726 if (row >= getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
3727 return false;
3728 }
3729
3730 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
3731 if (rowMetadata && typeof rowMetadata.selectable !== "undefined") {
3732 return !!rowMetadata.selectable;
3733 }
3734
3735 var columnMetadata = rowMetadata && rowMetadata.columns && (rowMetadata.columns[columns[cell].id] || rowMetadata.columns[cell]);
3736 if (columnMetadata && typeof columnMetadata.selectable !== "undefined") {
3737 return !!columnMetadata.selectable;
3738 }
3739
3740 return !!columns[cell].selectable;
3741 }
3742
3743 function gotoCell(row, cell, forceEdit) {
3744 if (!initialized) { return; }
3745 if (!canCellBeActive(row, cell)) {
3746 return;
3747 }
3748
3749 if (!getEditorLock().commitCurrentEdit()) {
3750 return;
3751 }
3752
3753 scrollCellIntoView(row, cell, false);
3754
3755 var newCell = getCellNode(row, cell);
3756
3757 // if selecting the 'add new' row, start editing right away
3758 setActiveCellInternal(newCell, (forceEdit || (row === getDataLength()) || options.autoEdit), null, options.editable);
3759
3760 // if no editor was created, set the focus back on the grid
3761 if (!currentEditor) {
3762 setFocus();
3763 }
3764 }
3765
3766
3767 //////////////////////////////////////////////////////////////////////////////////////////////
3768 // IEditor implementation for the editor lock
3769
3770 function commitCurrentEdit() {
3771 var item = getDataItem(activeRow);
3772 var column = columns[activeCell];
3773
3774 if (currentEditor) {
3775 if (currentEditor.isValueChanged()) {
3776 var validationResults = currentEditor.validate();
3777
3778 if (validationResults.valid) {
3779 if (activeRow < getDataLength()) {
3780 var editCommand = {
3781 row: activeRow,
3782 cell: activeCell,
3783 editor: currentEditor,
3784 serializedValue: currentEditor.serializeValue(),
3785 prevSerializedValue: serializedEditorValue,
3786 execute: function () {
3787 this.editor.applyValue(item, this.serializedValue);
3788 updateRow(this.row);
3789 trigger(self.onCellChange, {
3790 row: this.row,
3791 cell: this.cell,
3792 item: item
3793 });
3794 },
3795 undo: function () {
3796 this.editor.applyValue(item, this.prevSerializedValue);
3797 updateRow(this.row);
3798 trigger(self.onCellChange, {
3799 row: this.row,
3800 cell: this.cell,
3801 item: item
3802 });
3803 }
3804 };
3805
3806 if (options.editCommandHandler) {
3807 makeActiveCellNormal();
3808 options.editCommandHandler(item, column, editCommand);
3809 } else {
3810 editCommand.execute();
3811 makeActiveCellNormal();
3812 }
3813
3814 } else {
3815 var newItem = {};
3816 currentEditor.applyValue(newItem, currentEditor.serializeValue());
3817 makeActiveCellNormal();
3818 trigger(self.onAddNewRow, {item: newItem, column: column});
3819 }
3820
3821 // check whether the lock has been re-acquired by event handlers
3822 return !getEditorLock().isActive();
3823 } else {
3824 // Re-add the CSS class to trigger transitions, if any.
3825 $(activeCellNode).removeClass("invalid");
3826 $(activeCellNode).width(); // force layout
3827 $(activeCellNode).addClass("invalid");
3828
3829 trigger(self.onValidationError, {
3830 editor: currentEditor,
3831 cellNode: activeCellNode,
3832 validationResults: validationResults,
3833 row: activeRow,
3834 cell: activeCell,
3835 column: column
3836 });
3837
3838 currentEditor.focus();
3839 return false;
3840 }
3841 }
3842
3843 makeActiveCellNormal();
3844 }
3845 return true;
3846 }
3847
3848 function cancelCurrentEdit() {
3849 makeActiveCellNormal();
3850 return true;
3851 }
3852
3853 function rowsToRanges(rows) {
3854 var ranges = [];
3855 var lastCell = columns.length - 1;
3856 for (var i = 0; i < rows.length; i++) {
3857 ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
3858 }
3859 return ranges;
3860 }
3861
3862 function getSelectedRows() {
3863 if (!selectionModel) {
3864 throw new Error("Selection model is not set");
3865 }
3866 return selectedRows;
3867 }
3868
3869 function setSelectedRows(rows) {
3870 if (!selectionModel) {
3871 throw new Error("Selection model is not set");
3872 }
3873 if (!grid.getEditorLock().isActive()) {
3874 selectionModel.setSelectedRanges(rowsToRanges(rows));
3875 }
3876 }
3877
3878
3879 //////////////////////////////////////////////////////////////////////////////////////////////
3880 // Debug
3881
3882 this.debug = function () {
3883 var s = "";
3884
3885 s += ("\n" + "counter_rows_rendered: " + counter_rows_rendered);
3886 s += ("\n" + "counter_rows_removed: " + counter_rows_removed);
3887 s += ("\n" + "renderedRows: " + renderedRows);
3888 s += ("\n" + "numVisibleRows: " + numVisibleRows);
3889 s += ("\n" + "maxSupportedCssHeight: " + maxSupportedCssHeight);
3890 s += ("\n" + "n(umber of pages): " + n);
3891 s += ("\n" + "(current) page: " + page);
3892 s += ("\n" + "page height (ph): " + ph);
3893 s += ("\n" + "vScrollDir: " + vScrollDir);
3894
3895 alert(s);
3896 };
3897
3898 // a debug helper to be able to access private members
3899 this.eval = function (expr) {
3900 return eval(expr);
3901 };
3902
3903 //////////////////////////////////////////////////////////////////////////////////////////////
3904 // Public API
3905
3906 $.extend(this, {
3907 "slickGridVersion": "2.3.21",
3908
3909 // Events
3910 "onScroll": new Slick.Event(),
3911 "onSort": new Slick.Event(),
3912 "onHeaderMouseEnter": new Slick.Event(),
3913 "onHeaderMouseLeave": new Slick.Event(),
3914 "onHeaderContextMenu": new Slick.Event(),
3915 "onHeaderClick": new Slick.Event(),
3916 "onHeaderCellRendered": new Slick.Event(),
3917 "onBeforeHeaderCellDestroy": new Slick.Event(),
3918 "onHeaderRowCellRendered": new Slick.Event(),
3919 "onFooterRowCellRendered": new Slick.Event(),
3920 "onBeforeHeaderRowCellDestroy": new Slick.Event(),
3921 "onBeforeFooterRowCellDestroy": new Slick.Event(),
3922 "onMouseEnter": new Slick.Event(),
3923 "onMouseLeave": new Slick.Event(),
3924 "onClick": new Slick.Event(),
3925 "onDblClick": new Slick.Event(),
3926 "onContextMenu": new Slick.Event(),
3927 "onKeyDown": new Slick.Event(),
3928 "onAddNewRow": new Slick.Event(),
3929 "onBeforeAppendCell": new Slick.Event(),
3930 "onValidationError": new Slick.Event(),
3931 "onViewportChanged": new Slick.Event(),
3932 "onColumnsReordered": new Slick.Event(),
3933 "onColumnsResized": new Slick.Event(),
3934 "onCellChange": new Slick.Event(),
3935 "onBeforeEditCell": new Slick.Event(),
3936 "onBeforeCellEditorDestroy": new Slick.Event(),
3937 "onBeforeDestroy": new Slick.Event(),
3938 "onActiveCellChanged": new Slick.Event(),
3939 "onActiveCellPositionChanged": new Slick.Event(),
3940 "onDragInit": new Slick.Event(),
3941 "onDragStart": new Slick.Event(),
3942 "onDrag": new Slick.Event(),
3943 "onDragEnd": new Slick.Event(),
3944 "onSelectedRowsChanged": new Slick.Event(),
3945 "onCellCssStylesChanged": new Slick.Event(),
3946 "onAutosizeColumns": new Slick.Event(),
3947
3948 // Methods
3949 "registerPlugin": registerPlugin,
3950 "unregisterPlugin": unregisterPlugin,
3951 "getColumns": getColumns,
3952 "setColumns": setColumns,
3953 "getColumnIndex": getColumnIndex,
3954 "updateColumnHeader": updateColumnHeader,
3955 "setSortColumn": setSortColumn,
3956 "setSortColumns": setSortColumns,
3957 "getSortColumns": getSortColumns,
3958 "autosizeColumns": autosizeColumns,
3959 "getOptions": getOptions,
3960 "setOptions": setOptions,
3961 "getData": getData,
3962 "getDataLength": getDataLength,
3963 "getDataItem": getDataItem,
3964 "setData": setData,
3965 "getSelectionModel": getSelectionModel,
3966 "setSelectionModel": setSelectionModel,
3967 "getSelectedRows": getSelectedRows,
3968 "setSelectedRows": setSelectedRows,
3969 "getContainerNode": getContainerNode,
3970 "updatePagingStatusFromView": updatePagingStatusFromView,
3971
3972 "render": render,
3973 "invalidate": invalidate,
3974 "invalidateRow": invalidateRow,
3975 "invalidateRows": invalidateRows,
3976 "invalidateAllRows": invalidateAllRows,
3977 "updateCell": updateCell,
3978 "updateRow": updateRow,
3979 "getViewport": getVisibleRange,
3980 "getRenderedRange": getRenderedRange,
3981 "resizeCanvas": resizeCanvas,
3982 "updateRowCount": updateRowCount,
3983 "scrollRowIntoView": scrollRowIntoView,
3984 "scrollRowToTop": scrollRowToTop,
3985 "scrollCellIntoView": scrollCellIntoView,
3986 "scrollColumnIntoView": scrollColumnIntoView,
3987 "getCanvasNode": getCanvasNode,
3988 "getUID": getUID,
3989 "getHeaderColumnWidthDiff": getHeaderColumnWidthDiff,
3990 "getScrollbarDimensions": getScrollbarDimensions,
3991 "getHeadersWidth": getHeadersWidth,
3992 "getCanvasWidth": getCanvasWidth,
3993 "focus": setFocus,
3994 "scrollTo": scrollTo,
3995
3996 "getCellFromPoint": getCellFromPoint,
3997 "getCellFromEvent": getCellFromEvent,
3998 "getActiveCell": getActiveCell,
3999 "setActiveCell": setActiveCell,
4000 "getActiveCellNode": getActiveCellNode,
4001 "getActiveCellPosition": getActiveCellPosition,
4002 "resetActiveCell": resetActiveCell,
4003 "editActiveCell": makeActiveCellEditable,
4004 "getCellEditor": getCellEditor,
4005 "getCellNode": getCellNode,
4006 "getCellNodeBox": getCellNodeBox,
4007 "canCellBeSelected": canCellBeSelected,
4008 "canCellBeActive": canCellBeActive,
4009 "navigatePrev": navigatePrev,
4010 "navigateNext": navigateNext,
4011 "navigateUp": navigateUp,
4012 "navigateDown": navigateDown,
4013 "navigateLeft": navigateLeft,
4014 "navigateRight": navigateRight,
4015 "navigatePageUp": navigatePageUp,
4016 "navigatePageDown": navigatePageDown,
4017 "navigateTop": navigateTop,
4018 "navigateBottom": navigateBottom,
4019 "navigateRowStart": navigateRowStart,
4020 "navigateRowEnd": navigateRowEnd,
4021 "gotoCell": gotoCell,
4022 "getTopPanel": getTopPanel,
4023 "setTopPanelVisibility": setTopPanelVisibility,
4024 "getPreHeaderPanel": getPreHeaderPanel,
4025 "setPreHeaderPanelVisibility": setPreHeaderPanelVisibility,
4026 "getHeader": getHeader,
4027 "getHeaderColumn": getHeaderColumn,
4028 "setHeaderRowVisibility": setHeaderRowVisibility,
4029 "getHeaderRow": getHeaderRow,
4030 "getHeaderRowColumn": getHeaderRowColumn,
4031 "setFooterRowVisibility": setFooterRowVisibility,
4032 "getFooterRow": getFooterRow,
4033 "getFooterRowColumn": getFooterRowColumn,
4034 "getGridPosition": getGridPosition,
4035 "flashCell": flashCell,
4036 "addCellCssStyles": addCellCssStyles,
4037 "setCellCssStyles": setCellCssStyles,
4038 "removeCellCssStyles": removeCellCssStyles,
4039 "getCellCssStyles": getCellCssStyles,
4040
4041 "init": finishInitialization,
4042 "destroy": destroy,
4043
4044 // IEditor implementation
4045 "getEditorLock": getEditorLock,
4046 "getEditController": getEditController
4047 });
4048
4049 init();
4050 }
4051}(jQuery));